From 619465a57f6a55a1747042a9bcedd54b26a76dd7 Mon Sep 17 00:00:00 2001 From: Rich Benner Date: Tue, 1 Nov 2016 16:03:19 +0000 Subject: [PATCH 01/17] MAXDOP & Cost Threshold for Parallelism #568 Feature request: add two separate alerts for: Cost Threshold is at the default of 5 MAXDOP is at the default of 0, and the system has more than 1 socket, or more than 8 cores per socket --- sp_Blitz.sql | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 8a054f960..85fd521c3 100755 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -136,7 +136,9 @@ AS ,@MsSinceWaitsCleared DECIMAL(38,0) ,@CpuMsSinceWaitsCleared DECIMAL(38,0) ,@ResultText NVARCHAR(MAX) - ,@crlf NVARCHAR(2); + ,@crlf NVARCHAR(2) + ,@Processors int + ,@NUMANodes int; SET @crlf = NCHAR(13) + NCHAR(10); @@ -1497,6 +1499,33 @@ AS LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name AND cdUsed.DefaultValue = cr.value_in_use WHERE cdUsed.name IS NULL; + /* Let's set variables so that our query is still SARGable */ + SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info) + 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 */ + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT cd.CheckID , + 200 AS Priority , + 'Default Server Config' AS FindingsGroup , + cr.name AS Finding , + 'http://BrentOzar.com/go/cxpacket' AS URL , + ( 'This sp_configure option has not been changed. Changing these may reduce CPU contention. See the link for further details.') + 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 From d000269dec72477caa49af6f6938d1d7e9c7fad7 Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 1 Nov 2016 15:12:34 -0400 Subject: [PATCH 02/17] Add ability to skip statistics checks (on by default) #566 --- sp_BlitzIndex.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7dda45e2d..83126d524 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -21,6 +21,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex @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, @ThresholdMB INT = 250 /* Number of megabytes that an object must be before we include it in basic results */, @@ -1458,6 +1459,8 @@ BEGIN TRY + IF @SkipStatistics = 0 + 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)) @@ -1582,6 +1585,8 @@ BEGIN TRY EXEC sp_executesql @dsql; END + END + IF (PARSENAME(@SQLServerProductVersion, 4) >= 10) BEGIN RAISERROR (N'Gathering Computed Column Info.',0,1) WITH NOWAIT; From efcd6b639062a2dee1e967e6cb419278d68a2614 Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 1 Nov 2016 15:13:26 -0400 Subject: [PATCH 03/17] Add hash join hints to speed up old stats syntax #566 --- sp_BlitzIndex.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 83126d524..62d076848 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1554,19 +1554,19 @@ BEGIN TRY NULL AS filter_definition' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si ON si.name = s.name - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc ON sc.object_id = s.object_id AND sc.stats_id = s.stats_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c ON c.object_id = sc.object_id AND c.column_id = sc.column_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj ON s.object_id = obj.object_id - JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch + INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas sch ON sch.schema_id = obj.schema_id - LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i + LEFT HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i ON i.object_id = s.object_id AND i.index_id = s.stats_id WHERE obj.is_ms_shipped = 0 From 790e5a63177ab0c7b0e07bf7ab840b6bdacb284a Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 1 Nov 2016 15:16:43 -0400 Subject: [PATCH 04/17] Add persisted info to computed columns #567 --- sp_BlitzIndex.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 62d076848..b753ae54f 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1601,7 +1601,8 @@ BEGIN TRY cc.is_computed, CASE WHEN cc.definition LIKE ''%dbo%'' THEN 1 ELSE 0 END AS is_function, ''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + - '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + '';'' AS [column_definition] + '' ADD '' + QUOTENAME(c.name) + '' AS '' + cc.definition + + CASE WHEN is_persisted = 1 THEN '' PERSISTED'' ELSE '''' END + '';'' 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 From dcf3391845a106a4f5b006411a8dd713289ee46a Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 1 Nov 2016 15:17:29 -0400 Subject: [PATCH 05/17] Add more inclusive check for scalar functions in computed columns #571 --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index b753ae54f..3a70d5369 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -1599,7 +1599,7 @@ BEGIN TRY cc.uses_database_collation, cc.is_persisted, cc.is_computed, - CASE WHEN cc.definition LIKE ''%dbo%'' THEN 1 ELSE 0 END AS is_function, + CASE WHEN cc.definition LIKE ''%.%'' 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 + '';'' AS [column_definition] From 87a11d660af253b4f0c4be38feb63c93ee1f4eee Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Wed, 2 Nov 2016 13:45:21 -0400 Subject: [PATCH 06/17] Stop flagging queries that get the minimum grant #557 --- sp_BlitzCache.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 49aca9965..08be0aca6 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -738,6 +738,7 @@ BEGIN END DECLARE @DurationFilter_i INT, + @MinMemoryPerQuery INT, @msg NVARCHAR(4000) ; RAISERROR (N'Setting up temporary tables for sp_BlitzCache',0,1) WITH NOWAIT; @@ -756,6 +757,7 @@ BEGIN RETURN; END +SELECT @MinMemoryPerQuery = c.value FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; SET @SortOrder = LOWER(@SortOrder); SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); @@ -2051,7 +2053,7 @@ SET frequent_execution = CASE WHEN ExecutionsPerMinute > @execution_threshold is_key_lookup_expensive = CASE WHEN QueryPlanCost > (@ctp / 2) AND key_lookup_cost >= QueryPlanCost * .5 THEN 1 END, is_remote_query_expensive = CASE WHEN remote_query_cost >= QueryPlanCost * .05 THEN 1 END, is_forced_serial = CASE WHEN is_forced_serial = 1 AND QueryPlanCost > (@ctp / 2) THEN 1 END, - is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > 0 THEN 1 END + is_unused_grant = CASE WHEN PercentMemoryGrantUsed <= @memory_grant_warning_percent AND MinGrantKB > @MinMemoryPerQuery THEN 1 END OPTION (RECOMPILE) ; From 0f601c97c899da734c6f3ec6767a7c5c6b0da455 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 3 Nov 2016 14:39:36 -0500 Subject: [PATCH 07/17] #568 sp_Blitz MAXDOP & CTFP check housekeeping CheckID 188, added more information about default settings, put it in its own IF branch for #SkipChecks. --- Documentation/sp_Blitz Checks by Priority.md | 1 + sp_Blitz.sql | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Documentation/sp_Blitz Checks by Priority.md b/Documentation/sp_Blitz Checks by Priority.md index af0399438..74e132dca 100644 --- a/Documentation/sp_Blitz Checks by Priority.md +++ b/Documentation/sp_Blitz Checks by Priority.md @@ -211,6 +211,7 @@ If you want to change anything about a check - the priority, finding, URL, or ID | 200 | Non-Default Server Config | Web Assistant Procedures | http://BrentOzar.com/go/conf | 1064 | | 200 | Non-Default Server Config | xp_cmdshell | http://BrentOzar.com/go/conf | 1065 | | 200 | Performance | Buffer Pool Extensions Enabled | http://BrentOzar.com/go/bpe | 174 | +| 200 | Performance | Default Parallelism Settings | http://BrentOzar.com/go/cxpacket | 188 | | 200 | Performance | In-Memory OLTP (Hekaton) In Use | http://BrentOzar.com/go/hekaton | 146 | | 200 | Performance | Old Compatibility Level | http://BrentOzar.com/go/compatlevel | 62 | | 200 | Performance | Snapshot Backups Occurring | http://BrentOzar.com/go/snaps | 178 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 85fd521c3..53026f97f 100755 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -1499,6 +1499,13 @@ AS 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 = 188 ) + BEGIN + /* Let's set variables so that our query is still SARGable */ SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info) SET @NUMANodes = (SELECT COUNT(1) @@ -1515,12 +1522,12 @@ AS URL , Details ) - SELECT cd.CheckID , + SELECT 188 AS CheckID , 200 AS Priority , - 'Default Server Config' AS FindingsGroup , + 'Performance' AS FindingsGroup , cr.name AS Finding , 'http://BrentOzar.com/go/cxpacket' AS URL , - ( 'This sp_configure option has not been changed. Changing these may reduce CPU contention. See the link for further details.') + ( '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 From 1ed9de37602448fcfa8864b07d83a0115b5f103f Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Thu, 3 Nov 2016 15:47:45 -0400 Subject: [PATCH 08/17] Fix quotename issue with long statistic filter definitions Closes #574 --- sp_BlitzIndex.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 3a70d5369..a14fd49d1 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -3225,7 +3225,7 @@ BEGIN; 'Filter Fixation', s.database_name, '' AS URL, - 'The statistic ' + QUOTENAME(s.statistics_name) + ' is filtered on ' + QUOTENAME(s.filter_definition) + '. It could be part of a filtered index, or just a filtered statistic. This is purely informational.' , + '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.' , QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_name) AS index_definition, 'N/A' AS secret_columns, 'N/A' AS index_usage_summary, From 23d08488e58432285945dcf6d8461ebfd8068379 Mon Sep 17 00:00:00 2001 From: Rich Benner Date: Fri, 4 Nov 2016 10:23:11 +0000 Subject: [PATCH 09/17] sp_BlitzIndex - Update Version Check Now validates for 2008+ rather than 2005+ --- sp_BlitzIndex.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index a14fd49d1..5aed359d9 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -689,9 +689,9 @@ BEGIN TRY IF (SELECT LEFT(@SQLServerProductVersion, CHARINDEX('.',@SQLServerProductVersion,0)-1 - )) <= 8 + )) <= 9 BEGIN - SET @msg=N'sp_BlitzIndex is only supported on SQL Server 2005 and higher. The version of this instance is: ' + @SQLServerProductVersion; + 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 @@ -3585,4 +3585,4 @@ BEGIN CATCH RETURN; END CATCH; -GO \ No newline at end of file +GO From d79ff3aa286608af989553ea7ce1daecb41bea2e Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Mon, 14 Nov 2016 18:33:56 -0500 Subject: [PATCH 10/17] Fix for 584 Closes #584 --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 08be0aca6..ebd07cbc6 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -757,7 +757,7 @@ BEGIN RETURN; END -SELECT @MinMemoryPerQuery = c.value FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; +SELECT @MinMemoryPerQuery = CONVERT(INT, c.value) FROM sys.configurations AS c WHERE c.name = 'min memory per query (KB)'; SET @SortOrder = LOWER(@SortOrder); SET @SortOrder = REPLACE(REPLACE(@SortOrder, 'average', 'avg'), '.', ''); @@ -1821,7 +1821,7 @@ SET key_lookup_cost = x.key_lookup_cost FROM ( SELECT qs.SqlHandle, - relop.value('/p:RelOp[1]/@EstimatedTotalSubtreeCost', 'float') AS key_lookup_cost + relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float') AS key_lookup_cost FROM #relop qs WHERE [relop].exist('/p:RelOp/p:IndexScan[(@Lookup[.="1"])]') = 1 ) AS x @@ -1835,7 +1835,7 @@ SET remote_query_cost = x.remote_query_cost FROM ( SELECT qs.SqlHandle, - relop.value('/p:RelOp[1]/@EstimatedTotalSubtreeCost', 'float') AS remote_query_cost + relop.value('sum(/p:RelOp/@EstimatedTotalSubtreeCost)', 'float') AS remote_query_cost FROM #relop qs WHERE [relop].exist('/p:RelOp[(@PhysicalOp[.="Remote Query"])]') = 1 ) AS x From 76e684073e01bf414f0e6b885ac5bff21369150c Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Mon, 14 Nov 2016 18:49:38 -0500 Subject: [PATCH 11/17] Fixes for #495 Closes #495 --- sp_BlitzCache.sql | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index ebd07cbc6..a89ffe4a8 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -145,6 +145,8 @@ CREATE TABLE ##bou_BlitzCacheProcs ( function_count INT, clr_function_count INT, is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, SetOptions VARCHAR(MAX), Warnings VARCHAR(MAX) ); @@ -732,6 +734,8 @@ BEGIN function_count INT, clr_function_count INT, is_table_variable BIT, + no_stats_warning BIT, + relop_warnings BIT, SetOptions VARCHAR(MAX), Warnings VARCHAR(MAX) ); @@ -1786,7 +1790,9 @@ UPDATE p SET busy_loops = CASE WHEN (x.estimated_executions / 100.0) > x.estimated_rows THEN 1 END , tvf_join = CASE WHEN x.tvf_join = 1 THEN 1 END , warning_no_join_predicate = CASE WHEN x.no_join_warning = 1 THEN 1 END, - p.is_table_variable = CASE WHEN x.is_table_variable = 1 THEN 1 END + is_table_variable = CASE WHEN x.is_table_variable = 1 THEN 1 END, + no_stats_warning = CASE WHEN x.no_stats_warning = 1 THEN 1 END, + relop_warnings = CASE WHEN x.relop_warnings = 1 THEN 1 END FROM ##bou_BlitzCacheProcs p JOIN ( SELECT qs.SqlHandle, @@ -1794,7 +1800,9 @@ FROM ##bou_BlitzCacheProcs p relop.value('sum(/p:RelOp/@EstimateRewinds)', 'float') + relop.value('sum(/p:RelOp/@EstimateRebinds)', 'float') + 1.0 AS estimated_executions , relop.exist('/p:RelOp[contains(@LogicalOp, "Join")]/*/p:RelOp[(@LogicalOp[.="Table-valued function"])]') AS tvf_join, relop.exist('/p:RelOp/p:Warnings[(@NoJoinPredicate[.="1"])]') AS no_join_warning, - relop.exist('/p:RelOp//*[local-name() = "Object"]/@Table[contains(., "@")]') AS is_table_variable + relop.exist('/p:RelOp//*[local-name() = "Object"]/@Table[contains(., "@")]') AS is_table_variable, + relop.exist('/p:RelOp/p:Warnings/p:ColumnsWithNoStatistics') AS no_stats_warning , + relop.exist('/p:RelOp/p:Warnings') AS relop_warnings FROM #relop qs ) AS x ON p.SqlHandle = x.SqlHandle OPTION (RECOMPILE); From d7810a52c79b7c86985ab36c2e84dd25ad536dfc Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Mon, 14 Nov 2016 19:25:01 -0500 Subject: [PATCH 12/17] Add actual warning section for stats and relop warnings Forgetful me --- sp_BlitzCache.sql | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index a89ffe4a8..11e7fa6a7 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2136,7 +2136,9 @@ SET Warnings = SUBSTRING( 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 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 , 2, 200000) OPTION (RECOMPILE) ; @@ -2450,7 +2452,9 @@ BEGIN CASE WHEN function_count > 0 IS NOT NULL 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 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 , 2, 200000) AS opserver_warning , ' + @nl ; END @@ -2955,6 +2959,32 @@ BEGIN 'No URL yet.', 'All modifications are single threaded, and selects have really low row estimates.') ; + IF EXISTS (SELECT 1/0 + FROM ##bou_BlitzCacheProcs p + WHERE p.no_stats_warning = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 35, + 100, + 'Columns with no statistics', + 'Poor cardinality estimates may ensue', + 'No URL yet.', + 'Sometimes this happens with indexed views, other times because auto create stats is turned off.') ; + + IF EXISTS (SELECT 1/0 + FROM ##bou_BlitzCacheProcs p + WHERE p.is_table_variable = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 36, + 100, + 'Operator Warnings', + 'SQL is throwing operator level plan warnings', + 'No URL yet.', + 'Check the plan for more details.') ; + IF EXISTS (SELECT 1/0 FROM #plan_creation p WHERE p.percent_24 > 0 From 76bb584c3b5ba4895d28236f3e7b92d98abbe3ac Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Mon, 14 Nov 2016 20:24:32 -0500 Subject: [PATCH 13/17] Fixes #583 Closes #583 --- sp_BlitzCache.sql | 122 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 11e7fa6a7..30510f3b4 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -147,6 +147,11 @@ CREATE TABLE ##bou_BlitzCacheProcs ( 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, SetOptions VARCHAR(MAX), Warnings VARCHAR(MAX) ); @@ -736,6 +741,11 @@ BEGIN 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, SetOptions VARCHAR(MAX), Warnings VARCHAR(MAX) ); @@ -1861,6 +1871,38 @@ ON b.QueryHash = qs.QueryHash CROSS APPLY qs.statement.nodes('/p:StmtCursor') AS n1(fn) OPTION (RECOMPILE) ; +;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 ##bou_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 +OPTION (RECOMPILE) ; + IF @v >= 12 BEGIN @@ -2138,7 +2180,12 @@ SET Warnings = SUBSTRING( 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 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 , 2, 200000) OPTION (RECOMPILE) ; @@ -2147,12 +2194,6 @@ SET Warnings = SUBSTRING( - - - - - - Results: IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL @@ -2452,9 +2493,13 @@ BEGIN CASE WHEN function_count > 0 IS NOT NULL 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_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 , 2, 200000) AS opserver_warning , ' + @nl ; END @@ -2974,7 +3019,7 @@ BEGIN IF EXISTS (SELECT 1/0 FROM ##bou_BlitzCacheProcs p - WHERE p.is_table_variable = 1 + WHERE p.relop_warnings = 1 AND SPID = @@SPID) INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) VALUES (@@SPID, @@ -2985,6 +3030,60 @@ BEGIN 'No URL yet.', 'Check the plan for more details.') ; + IF EXISTS (SELECT 1/0 + FROM ##bou_BlitzCacheProcs p + WHERE p.is_table_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 37, + 100, + 'Table Scans', + 'Your database has HEAPs', + 'No URL yet.', + 'This may not be a problem. Run sp_BlitzIndex for more information.') ; + + IF EXISTS (SELECT 1/0 + FROM ##bou_BlitzCacheProcs p + WHERE p.backwards_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 38, + 100, + 'Backwards Scans', + 'Indexes are being read backwards', + 'No URL yet.', + '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 ##bou_BlitzCacheProcs p + WHERE p.forced_index = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 39, + 100, + 'Index forcing', + 'Someone is using hints to force index usage', + 'No URL yet.', + 'This can cause inefficient plans, and will prevent missing index requests.') ; + + IF EXISTS (SELECT 1/0 + FROM ##bou_BlitzCacheProcs p + WHERE p.forced_seek = 1 + OR p.forced_scan = 1 + AND SPID = @@SPID) + INSERT INTO ##bou_BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL, Details) + VALUES (@@SPID, + 40, + 100, + 'Seek/Scan forcing', + 'Someone is using hints to force index seeks/scans', + 'No URL yet.', + 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; + + IF EXISTS (SELECT 1/0 FROM #plan_creation p WHERE p.percent_24 > 0 @@ -3000,7 +3099,6 @@ BEGIN 'If 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 #trace_flags AS tf WHERE tf.global_trace_flags IS NOT NULL From 0f262e13f427da1b5a1bdf0df7a76c42aefa6b88 Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 15 Nov 2016 10:50:04 -0500 Subject: [PATCH 14/17] Add URL for memory grants Yeehaw --- sp_BlitzCache.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 30510f3b4..5faa1c66a 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2961,7 +2961,7 @@ BEGIN 100, 'Unused memory grants', 'Queries are asking for more memory than they''re using', - 'No URL yet.', + '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 ff298b681a5a29dab0c6d50da6afa1e5dd5177b7 Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 15 Nov 2016 11:46:15 -0500 Subject: [PATCH 15/17] More URLs Hooray! --- sp_BlitzCache.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 5faa1c66a..d1b213708 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -2974,7 +2974,7 @@ BEGIN 100, 'Compute Scalar That References A Function', 'This could be trouble if you''re using Scalar Functions or MSTVFs', - 'No URL yet.', + '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 @@ -2987,7 +2987,7 @@ BEGIN 100, 'Compute Scalar That References A CLR Function', 'This could be trouble if your CLR functions perform data access', - 'No URL yet.', + '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') ; @@ -3001,7 +3001,7 @@ BEGIN 100, 'Table Variables detected', 'Beware nasty side effects', - 'No URL yet.', + '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 d7e016c4fadb3987d144e6cdb01e5157789725a5 Mon Sep 17 00:00:00 2001 From: BlitzErik Date: Tue, 15 Nov 2016 12:44:24 -0500 Subject: [PATCH 16/17] More URLs Hooray! --- sp_BlitzCache.sql | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index d1b213708..ea38ba9d1 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -3014,7 +3014,7 @@ BEGIN 100, 'Columns with no statistics', 'Poor cardinality estimates may ensue', - 'No URL yet.', + '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 @@ -3027,7 +3027,7 @@ BEGIN 100, 'Operator Warnings', 'SQL is throwing operator level plan warnings', - 'No URL yet.', + 'http://brentozar.com/blitzcache/query-plan-warnings/', 'Check the plan for more details.') ; IF EXISTS (SELECT 1/0 @@ -3040,7 +3040,7 @@ BEGIN 100, 'Table Scans', 'Your database has HEAPs', - 'No URL yet.', + '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 @@ -3053,7 +3053,7 @@ BEGIN 100, 'Backwards Scans', 'Indexes are being read backwards', - 'No URL yet.', + '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 @@ -3066,7 +3066,7 @@ BEGIN 100, 'Index forcing', 'Someone is using hints to force index usage', - 'No URL yet.', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', 'This can cause inefficient plans, and will prevent missing index requests.') ; IF EXISTS (SELECT 1/0 @@ -3080,7 +3080,7 @@ BEGIN 100, 'Seek/Scan forcing', 'Someone is using hints to force index seeks/scans', - 'No URL yet.', + 'https://www.brentozar.com/blitzcache/optimizer-forcing/', 'This can cause inefficient plans by taking seek vs scan choice away from the optimizer.') ; @@ -3095,7 +3095,7 @@ BEGIN 254, 'Plan Cache Information', 'You have ' + CONVERT(NVARCHAR(10), p.percent_24) + '% plans created in the past 24 hours, and ' + CONVERT(NVARCHAR(10), p.percent_4) + '% created in the past 4 hours.', - 'No URL yet.', + '', 'If these percentages are high, it may be a sign of memory pressure or plan cache instability.' FROM #plan_creation p ; From e16c50f2b4bcc1825e9dfc41658eab1aad1bd30b Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Tue, 15 Nov 2016 14:05:45 -0800 Subject: [PATCH 17/17] Bumping version numbers for today's release --- sp_Blitz.sql | 2 +- sp_BlitzIndex.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 53026f97f..a0e068858 100755 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -29,7 +29,7 @@ ALTER PROCEDURE [dbo].[sp_Blitz] AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SET @VersionDate = '20161022'; + SET @VersionDate = '20161115'; SET @OutputType = UPPER(@OutputType); IF @Help = 1 PRINT ' diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 5aed359d9..790175b90 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -36,8 +36,8 @@ AS SET NOCOUNT ON; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @Version VARCHAR(30); -SET @Version = '4.4'; -SET @VersionDate = '20161022'; +SET @Version = '4.5'; +SET @VersionDate = '20161115'; IF @Help = 1 PRINT ' /* sp_BlitzIndex from http://FirstResponderKit.org