Skip to content

Commit 1b6014b

Browse files
authored
Merge pull request #3352 from BrentOzarULTD/3345_sp_BlitzCache_duplicate_plans_2
#3345 sp_BlitzCache duplicate plans 2
2 parents 0a9a6ec + ab595c2 commit 1b6014b

1 file changed

Lines changed: 61 additions & 49 deletions

File tree

sp_BlitzCache.sql

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ IF @Help = 1
354354
UNION ALL
355355
SELECT N'@SortOrder',
356356
N'VARCHAR(10)',
357-
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.'
357+
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.'
358358

359359
UNION ALL
360360
SELECT N'@UseTriggersAnyway',
@@ -776,12 +776,30 @@ END;
776776
/* Lets get @SortOrder set to lower case here for comparisons later */
777777
SET @SortOrder = LOWER(@SortOrder);
778778

779+
/* Set @Top based on sort */
780+
IF (
781+
@Top IS NULL
782+
AND @SortOrder IN ( 'all', 'all sort' )
783+
)
784+
BEGIN
785+
SET @Top = 5;
786+
END;
787+
788+
IF (
789+
@Top IS NULL
790+
AND @SortOrder NOT IN ( 'all', 'all sort' )
791+
)
792+
BEGIN
793+
SET @Top = 10;
794+
END;
795+
796+
779797
/* If they want to sort by query hash, populate the @OnlyQueryHashes list for them */
780798
IF @SortOrder LIKE 'query hash%'
781799
BEGIN
782800
RAISERROR('Beginning query hash sort', 0, 1) WITH NOWAIT;
783801

784-
SELECT qs.query_hash,
802+
SELECT TOP(@Top) qs.query_hash,
785803
MAX(qs.max_worker_time) AS max_worker_time,
786804
COUNT_BIG(*) AS records
787805
INTO #query_hash_grouped
@@ -810,52 +828,33 @@ IF @SortOrder LIKE 'query hash%'
810828
END
811829

812830

813-
/* If they want to sort by duplicates, populate the @OnlySqlHandles list for them */
831+
/* If they want to sort by duplicate, create a table with the worst offenders - issue #3345 */
814832
IF @SortOrder LIKE 'duplicate%'
815833
BEGIN
816834
RAISERROR('Beginning duplicate query hash sort', 0, 1) WITH NOWAIT;
817835

818-
SELECT TOP(@Top) qs.query_hash,
819-
MAX(qs.sql_handle) AS max_sql_handle,
820-
COUNT_BIG(*) AS records
821-
INTO #duplicate_grouped
822-
FROM sys.dm_exec_query_stats AS qs
823-
CROSS APPLY ( SELECT pa.value
824-
FROM sys.dm_exec_plan_attributes(qs.plan_handle) AS pa
825-
WHERE pa.attribute = 'dbid' ) AS ca
826-
GROUP BY qs.query_hash, ca.value
827-
HAVING COUNT_BIG(*) > 100
828-
ORDER BY records DESC;
829-
830-
SELECT TOP (1)
831-
@OnlySqlHandles = STUFF((SELECT DISTINCT N',' + CONVERT(NVARCHAR(MAX), qhg.max_sql_handle, 1)
832-
FROM #duplicate_grouped AS qhg
833-
WHERE qhg.max_sql_handle <> 0x00
834-
FOR XML PATH(N''), TYPE).value(N'.[1]', N'NVARCHAR(MAX)'), 1, 1, N'')
835-
OPTION(RECOMPILE);
836-
837-
SET @SortOrder = 'cpu';
836+
/* Find the query hashes that are the most duplicated */
837+
WITH MostCommonQueries AS (
838+
SELECT TOP(@Top) qs.query_hash,
839+
COUNT_BIG(*) AS plans
840+
FROM sys.dm_exec_query_stats AS qs
841+
GROUP BY qs.query_hash
842+
HAVING COUNT_BIG(*) > 100
843+
ORDER BY COUNT_BIG(*) DESC
844+
)
845+
SELECT mcq_recent.sql_handle, mcq_recent.plan_handle, mcq_recent.creation_time AS duplicate_creation_time, mcq.plans
846+
INTO #duplicate_query_filter
847+
FROM MostCommonQueries mcq
848+
CROSS APPLY ( SELECT TOP 1 qs.sql_handle, qs.plan_handle, qs.creation_time
849+
FROM sys.dm_exec_query_stats qs
850+
WHERE qs.query_hash = mcq.query_hash
851+
ORDER BY qs.creation_time DESC) AS mcq_recent
852+
OPTION (RECOMPILE);
838853

854+
SET @minimumExecutionCount = 0;
839855
END
840856

841857

842-
/* Set @Top based on sort */
843-
IF (
844-
@Top IS NULL
845-
AND @SortOrder IN ( 'all', 'all sort' )
846-
)
847-
BEGIN
848-
SET @Top = 5;
849-
END;
850-
851-
IF (
852-
@Top IS NULL
853-
AND @SortOrder NOT IN ( 'all', 'all sort' )
854-
)
855-
BEGIN
856-
SET @Top = 10;
857-
END;
858-
859858
/* validate user inputs */
860859
IF @Top IS NULL
861860
OR @SortOrder IS NULL
@@ -951,14 +950,15 @@ SET @SortOrder = CASE
951950
WHEN @SortOrder IN ('spill') THEN 'spills'
952951
WHEN @SortOrder IN ('avg spill') THEN 'avg spills'
953952
WHEN @SortOrder IN ('execution') THEN 'executions'
953+
WHEN @SortOrder IN ('duplicates') THEN 'duplicate'
954954
ELSE @SortOrder END
955955

956956
RAISERROR(N'Checking sort order', 0, 1) WITH NOWAIT;
957957
IF @SortOrder NOT IN ('cpu', 'avg cpu', 'reads', 'avg reads', 'writes', 'avg writes',
958958
'duration', 'avg duration', 'executions', 'avg executions',
959959
'compiles', 'memory grant', 'avg memory grant', 'unused grant',
960960
'spills', 'avg spills', 'all', 'all avg', 'sp_BlitzIndex',
961-
'query hash')
961+
'query hash', 'duplicate')
962962
BEGIN
963963
RAISERROR(N'Invalid sort order chosen, reverting to cpu', 16, 1) WITH NOWAIT;
964964
SET @SortOrder = 'cpu';
@@ -1801,6 +1801,10 @@ FROM (SELECT TOP (@Top) x.*, xpa.*,
18011801
CROSS APPLY (SELECT * FROM sys.dm_exec_plan_attributes(x.plan_handle) AS ixpa
18021802
WHERE ixpa.attribute = ''dbid'') AS xpa ' + @nl ;
18031803

1804+
IF @SortOrder = 'duplicate' /* Issue #3345 */
1805+
BEGIN
1806+
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 ;
1807+
END
18041808

18051809
IF @VersionShowsAirQuoteActualPlans = 1
18061810
BEGIN
@@ -1859,7 +1863,6 @@ BEGIN
18591863
END;
18601864
/* end filtering for query hashes */
18611865

1862-
18631866
IF @DurationFilter IS NOT NULL
18641867
BEGIN
18651868
RAISERROR(N'Setting duration filter', 0, 1) WITH NOWAIT;
@@ -1895,6 +1898,7 @@ SELECT @body += N' ORDER BY ' +
18951898
WHEN N'memory grant' THEN N'max_grant_kb'
18961899
WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb'
18971900
WHEN N'spills' THEN N'max_spills'
1901+
WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */
18981902
/* And now the averages */
18991903
WHEN N'avg cpu' THEN N'total_worker_time / execution_count'
19001904
WHEN N'avg reads' THEN N'total_logical_reads / execution_count'
@@ -1936,7 +1940,6 @@ IF @VersionShowsAirQuoteActualPlans = 1
19361940

19371941
SET @body_where += N' AND pa.attribute = ' + QUOTENAME('dbid', @q ) + @nl ;
19381942

1939-
19401943
IF @NoobSaibot = 1
19411944
BEGIN
19421945
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;
22452248
IF (@QueryFilter = 'all'
22462249
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
22472250
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0)
2248-
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant'))
2251+
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */
22492252
OR (LEFT(@QueryFilter, 3) = 'pro')
22502253
BEGIN
22512254
SET @sql += @insert_list;
@@ -2266,7 +2269,7 @@ IF (@v >= 13
22662269
AND @QueryFilter = 'all'
22672270
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
22682271
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0)
2269-
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant'))
2272+
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */
22702273
AND (@SortOrder NOT IN ('spills', 'avg spills'))
22712274
OR (LEFT(@QueryFilter, 3) = 'fun')
22722275
BEGIN
@@ -2309,7 +2312,7 @@ IF (@UseTriggersAnyway = 1 OR @v >= 11)
23092312
AND (SELECT COUNT(*) FROM #only_query_hashes) = 0
23102313
AND (SELECT COUNT(*) FROM #ignore_query_hashes) = 0
23112314
AND (@QueryFilter = 'all')
2312-
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant'))
2315+
AND (@SortOrder NOT IN ('memory grant', 'avg memory grant', 'unused grant', 'duplicate')) /* Issue #3345 added 'duplicate' */
23132316
BEGIN
23142317
RAISERROR (N'Adding SQL to collect trigger stats.',0,1) WITH NOWAIT;
23152318

@@ -2341,6 +2344,7 @@ SELECT @sort = CASE @SortOrder WHEN N'cpu' THEN N'total_worker_time'
23412344
WHEN N'memory grant' THEN N'max_grant_kb'
23422345
WHEN N'unused grant' THEN N'max_grant_kb - max_used_grant_kb'
23432346
WHEN N'spills' THEN N'max_spills'
2347+
WHEN N'duplicate' THEN N'total_worker_time' /* Issue #3345 */
23442348
/* And now the averages */
23452349
WHEN N'avg cpu' THEN N'total_worker_time / execution_count'
23462350
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'
24022406
WHEN N'memory grant' THEN N'MaxGrantKB'
24032407
WHEN N'unused grant' THEN N'MaxGrantKB - MaxUsedGrantKB'
24042408
WHEN N'spills' THEN N'MaxSpills'
2409+
WHEN N'duplicate' THEN N'TotalCPU' /* Issue #3345 */
24052410
/* And now the averages */
24062411
WHEN N'avg cpu' THEN N'TotalCPU / ExecutionCount'
24072412
WHEN N'avg reads' THEN N'TotalReads / ExecutionCount'
@@ -2420,6 +2425,7 @@ SELECT @sql = REPLACE(@sql, '#sortable#', @sort);
24202425

24212426
IF @Debug = 1
24222427
BEGIN
2428+
PRINT N'Printing dynamic SQL stored in @sql: ';
24232429
PRINT SUBSTRING(@sql, 0, 4000);
24242430
PRINT SUBSTRING(@sql, 4000, 8000);
24252431
PRINT SUBSTRING(@sql, 8000, 12000);
@@ -4990,6 +4996,7 @@ BEGIN
49904996
WHEN N'memory grant' THEN N' MaxGrantKB'
49914997
WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB'
49924998
WHEN N'spills' THEN N' MaxSpills'
4999+
WHEN N'duplicate' THEN N' plan_multiple_plans ' /* Issue #3345 */
49935000
WHEN N'avg cpu' THEN N' AverageCPU'
49945001
WHEN N'avg reads' THEN N' AverageReads'
49955002
WHEN N'avg writes' THEN N' AverageWrites'
@@ -5001,8 +5008,14 @@ BEGIN
50015008

50025009
SET @sql += N' OPTION (RECOMPILE) ; ';
50035010

5011+
IF @sql IS NULL
5012+
BEGIN
5013+
RAISERROR('@sql is null, which means dynamic SQL generation went terribly wrong', 0, 1) WITH NOWAIT;
5014+
END
5015+
50045016
IF @Debug = 1
50055017
BEGIN
5018+
RAISERROR('Printing @sql, the dynamic SQL we generated:', 0, 1) WITH NOWAIT;
50065019
PRINT SUBSTRING(@sql, 0, 4000);
50075020
PRINT SUBSTRING(@sql, 4000, 8000);
50085021
PRINT SUBSTRING(@sql, 8000, 12000);
@@ -5214,8 +5227,6 @@ BEGIN
52145227
[Remove SQL Handle From Cache]';
52155228
END;
52165229

5217-
5218-
52195230
SET @sql = N'
52205231
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
52215232
SELECT TOP (@Top) ' + @columns + @nl + N'
@@ -5240,7 +5251,8 @@ SELECT @sql += N' ORDER BY ' + CASE @SortOrder WHEN N'cpu' THEN N' TotalCPU '
52405251
WHEN N'compiles' THEN N' PlanCreationTime '
52415252
WHEN N'memory grant' THEN N' MaxGrantKB'
52425253
WHEN N'unused grant' THEN N' MaxGrantKB - MaxUsedGrantKB '
5243-
WHEN N'spills' THEN N' MaxSpills'
5254+
WHEN N'duplicate' THEN N' plan_multiple_plans '
5255+
WHEN N'spills' THEN N' MaxSpills '
52445256
WHEN N'avg cpu' THEN N' AverageCPU'
52455257
WHEN N'avg reads' THEN N' AverageReads'
52465258
WHEN N'avg writes' THEN N' AverageWrites'

0 commit comments

Comments
 (0)