@@ -41,7 +41,7 @@ CREATE TABLE ##BlitzCacheResults (
4141 CheckID INT ,
4242 Priority TINYINT ,
4343 FindingsGroup VARCHAR (50 ),
44- Finding VARCHAR (200 ),
44+ Finding VARCHAR (500 ),
4545 URL VARCHAR (200 ),
4646 Details VARCHAR (4000 )
4747);
@@ -1249,6 +1249,9 @@ IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL
12491249IF OBJECT_ID (' tempdb..#ReadableDBs' ) IS NOT NULL
12501250 DROP TABLE #ReadableDBs;
12511251
1252+ IF OBJECT_ID (' tempdb..#plan_usage' ) IS NOT NULL
1253+ DROP TABLE #plan_usage;
1254+
12521255CREATE TABLE #only_query_hashes (
12531256 query_hash BINARY (8 )
12541257);
@@ -1531,6 +1534,18 @@ CREATE TABLE #ReadableDBs
15311534database_id INT
15321535);
15331536
1537+
1538+ CREATE TABLE #plan_usage
1539+ (
1540+ duplicate_plan_handles BIGINT NULL ,
1541+ percent_duplicate NUMERIC (7 , 2 ) NULL ,
1542+ single_use_plan_count BIGINT NULL ,
1543+ percent_single NUMERIC (7 , 2 ) NULL ,
1544+ total_plans BIGINT NULL ,
1545+ spid INT
1546+ );
1547+
1548+
15341549IF EXISTS (SELECT * FROM sys .all_objects o WHERE o .name = ' dm_hadr_database_replica_states' )
15351550BEGIN
15361551 RAISERROR (' Checking for Read intent databases to exclude' ,0 ,0 ) WITH NOWAIT ;
@@ -1556,6 +1571,48 @@ FROM x
15561571OPTION (RECOMPILE );
15571572
15581573
1574+ RAISERROR (N ' Checking for single use plans and plans with many queries' , 0 , 1 ) WITH NOWAIT ;
1575+ WITH total_plans AS
1576+ (
1577+ SELECT COUNT_BIG (* ) AS total_plans
1578+ FROM sys .dm_exec_query_stats AS deqs
1579+ ),
1580+ many_plans AS
1581+ (
1582+ SELECT SUM (x .duplicate_plan_handles ) AS duplicate_plan_handles
1583+ FROM (
1584+ SELECT COUNT_BIG (DISTINCT plan_handle) AS duplicate_plan_handles
1585+ FROM sys .dm_exec_query_stats qs
1586+ CROSS APPLY sys .dm_exec_plan_attributes (qs .plan_handle ) pa
1587+ WHERE pa .attribute = N ' dbid'
1588+ GROUP BY qs .query_hash , pa .value
1589+ HAVING COUNT_BIG (DISTINCT plan_handle) > 5
1590+ ) AS x
1591+ ),
1592+ single_use_plans AS
1593+ (
1594+ SELECT COUNT_BIG (* ) AS single_use_plan_count
1595+ FROM sys .dm_exec_cached_plans AS cp
1596+ WHERE cp .usecounts = 1
1597+ AND cp .objtype = N ' Adhoc'
1598+ AND EXISTS ( SELECT 1 / 0
1599+ FROM sys .configurations AS c
1600+ WHERE c .name = N ' optimize for ad hoc workloads'
1601+ AND c .value_in_use = 0 )
1602+ HAVING COUNT_BIG (* ) > 1
1603+ )
1604+ INSERT #plan_usage ( duplicate_plan_handles, percent_duplicate, single_use_plan_count, percent_single, total_plans, spid )
1605+ SELECT m .duplicate_plan_handles ,
1606+ CONVERT (DECIMAL (3 ,2 ), m .duplicate_plan_handles / (1 . * NULLIF (t .total_plans , 0 ))) * 100 . AS percent_duplicate,
1607+ s .single_use_plan_count ,
1608+ CONVERT (DECIMAL (3 ,2 ), s .single_use_plan_count / (1 . * NULLIF (t .total_plans , 0 ))) * 100 . AS percent_single,
1609+ t .total_plans ,
1610+ @@SPID
1611+ FROM many_plans AS m,
1612+ single_use_plans AS s,
1613+ total_plans AS t;
1614+
1615+
15591616SET @OnlySqlHandles = LTRIM (RTRIM (@OnlySqlHandles)) ;
15601617SET @OnlyQueryHashes = LTRIM (RTRIM (@OnlyQueryHashes)) ;
15611618SET @IgnoreQueryHashes = LTRIM (RTRIM (@IgnoreQueryHashes)) ;
@@ -6051,6 +6108,44 @@ BEGIN
60516108 ' If these percentages are high, it may be a sign of memory pressure or plan cache instability.'
60526109 FROM #plan_creation p ;
60536110
6111+ IF EXISTS (SELECT 1 / 0
6112+ FROM #plan_usage p
6113+ WHERE p .percent_duplicate > 5
6114+ AND spid = @@SPID )
6115+ INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL , Details)
6116+ SELECT spid,
6117+ 999 ,
6118+ 254 ,
6119+ ' Plan Cache Information' ,
6120+ ' You have ' + CONVERT (NVARCHAR (10 ), p .total_plans )
6121+ + ' plans in your cache, and '
6122+ + CONVERT (NVARCHAR (10 ), p .percent_duplicate )
6123+ + ' % are duplicates with more than 5 entries'
6124+ + ' , meaning similar queries are generating the same plan repeatedly.'
6125+ + ' Forced Parameterization may fix the issue.' ,
6126+ ' https://www.brentozar.com/archive/2018/03/why-multiple-plans-for-one-query-are-bad/' ,
6127+ ' To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = '' query hash'' ; '
6128+ FROM #plan_usage AS p ;
6129+
6130+ IF EXISTS (SELECT 1 / 0
6131+ FROM #plan_usage p
6132+ WHERE p .percent_single > 5
6133+ AND spid = @@SPID )
6134+ INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL , Details)
6135+ SELECT spid,
6136+ 999 ,
6137+ 254 ,
6138+ ' Plan Cache Information' ,
6139+ ' You have ' + CONVERT (NVARCHAR (10 ), p .total_plans )
6140+ + ' in your cache, and '
6141+ + CONVERT (NVARCHAR (10 ), p .percent_single )
6142+ + ' % are single use plans'
6143+ + ' , meaning SQL Server thinks it'' s seeing a lot of "new" queries and creating plans for them.'
6144+ + ' Forced Parameterization and Optimize For Ad Hoc Workloads may fix the issue.' ,
6145+ ' https://www.brentozar.com/blitz/single-use-plans-procedure-cache/' ,
6146+ ' To find troublemakers, use: EXEC sp_BlitzCache @SortOrder = '' recent compilations'' ; '
6147+ FROM #plan_usage AS p ;
6148+
60546149 IF @is_tokenstore_big = 1
60556150 INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL , Details)
60566151 SELECT @@SPID ,
@@ -6138,15 +6233,15 @@ IF @Debug = 1
61386233 FROM ##BlitzCacheProcs
61396234 OPTION ( RECOMPILE );
61406235
6141- SELECT ' #statements' AS table_name, *
6236+ SELECT ' #statements' AS table_name, *
61426237 FROM #statements AS s
61436238 OPTION (RECOMPILE );
61446239
6145- SELECT ' #query_plan' AS table_name, *
6240+ SELECT ' #query_plan' AS table_name, *
61466241 FROM #query_plan AS qp
61476242 OPTION (RECOMPILE );
61486243
6149- SELECT ' #relop' AS table_name, *
6244+ SELECT ' #relop' AS table_name, *
61506245 FROM #relop AS r
61516246 OPTION (RECOMPILE );
61526247
@@ -6230,6 +6325,10 @@ IF @Debug = 1
62306325 FROM #trace_flags
62316326 OPTION ( RECOMPILE );
62326327
6328+ SELECT ' #plan_usage' AS table_name, *
6329+ FROM #plan_usage
6330+ OPTION ( RECOMPILE );
6331+
62336332 END ;
62346333
62356334 IF @OutputDatabaseName IS NOT NULL
0 commit comments