@@ -41,7 +41,7 @@ CREATE TABLE ##BlitzCacheResults (
41
41
CheckID INT ,
42
42
Priority TINYINT ,
43
43
FindingsGroup VARCHAR (50 ),
44
- Finding VARCHAR (200 ),
44
+ Finding VARCHAR (500 ),
45
45
URL VARCHAR (200 ),
46
46
Details VARCHAR (4000 )
47
47
);
@@ -1249,6 +1249,9 @@ IF OBJECT_ID('tempdb..#index_spool_ugly') IS NOT NULL
1249
1249
IF OBJECT_ID (' tempdb..#ReadableDBs' ) IS NOT NULL
1250
1250
DROP TABLE #ReadableDBs;
1251
1251
1252
+ IF OBJECT_ID (' tempdb..#plan_usage' ) IS NOT NULL
1253
+ DROP TABLE #plan_usage;
1254
+
1252
1255
CREATE TABLE #only_query_hashes (
1253
1256
query_hash BINARY (8 )
1254
1257
);
@@ -1531,6 +1534,18 @@ CREATE TABLE #ReadableDBs
1531
1534
database_id INT
1532
1535
);
1533
1536
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
+
1534
1549
IF EXISTS (SELECT * FROM sys .all_objects o WHERE o .name = ' dm_hadr_database_replica_states' )
1535
1550
BEGIN
1536
1551
RAISERROR (' Checking for Read intent databases to exclude' ,0 ,0 ) WITH NOWAIT ;
@@ -1556,6 +1571,48 @@ FROM x
1556
1571
OPTION (RECOMPILE );
1557
1572
1558
1573
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
+
1559
1616
SET @OnlySqlHandles = LTRIM (RTRIM (@OnlySqlHandles)) ;
1560
1617
SET @OnlyQueryHashes = LTRIM (RTRIM (@OnlyQueryHashes)) ;
1561
1618
SET @IgnoreQueryHashes = LTRIM (RTRIM (@IgnoreQueryHashes)) ;
@@ -6051,6 +6108,44 @@ BEGIN
6051
6108
' If these percentages are high, it may be a sign of memory pressure or plan cache instability.'
6052
6109
FROM #plan_creation p ;
6053
6110
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
+
6054
6149
IF @is_tokenstore_big = 1
6055
6150
INSERT INTO ##BlitzCacheResults (SPID, CheckID, Priority, FindingsGroup, Finding, URL , Details)
6056
6151
SELECT @@SPID ,
@@ -6138,15 +6233,15 @@ IF @Debug = 1
6138
6233
FROM ##BlitzCacheProcs
6139
6234
OPTION ( RECOMPILE );
6140
6235
6141
- SELECT ' #statements' AS table_name, *
6236
+ SELECT ' #statements' AS table_name, *
6142
6237
FROM #statements AS s
6143
6238
OPTION (RECOMPILE );
6144
6239
6145
- SELECT ' #query_plan' AS table_name, *
6240
+ SELECT ' #query_plan' AS table_name, *
6146
6241
FROM #query_plan AS qp
6147
6242
OPTION (RECOMPILE );
6148
6243
6149
- SELECT ' #relop' AS table_name, *
6244
+ SELECT ' #relop' AS table_name, *
6150
6245
FROM #relop AS r
6151
6246
OPTION (RECOMPILE );
6152
6247
@@ -6230,6 +6325,10 @@ IF @Debug = 1
6230
6325
FROM #trace_flags
6231
6326
OPTION ( RECOMPILE );
6232
6327
6328
+ SELECT ' #plan_usage' AS table_name, *
6329
+ FROM #plan_usage
6330
+ OPTION ( RECOMPILE );
6331
+
6233
6332
END ;
6234
6333
6235
6334
IF @OutputDatabaseName IS NOT NULL
0 commit comments