Skip to content

Commit 567e199

Browse files
committed
Add checks for caching issues
This update adds two plan cache checks: - Single use plans - Queries with multiple plans (> 5) As a side effect, I had to expand the finding column in ##BlitzCacheResults a little bit. It also adds a new table called #plan_usage into the mix to hold results. As submitted, these will fire when the plan cache is more than 5% inhabited by either species. Feel free to change this. I'm terrible at deciding baseline numbers.
1 parent eb92d12 commit 567e199

File tree

1 file changed

+103
-4
lines changed

1 file changed

+103
-4
lines changed

sp_BlitzCache.sql

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
12491249
IF 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+
12521255
CREATE TABLE #only_query_hashes (
12531256
query_hash BINARY(8)
12541257
);
@@ -1531,6 +1534,18 @@ CREATE TABLE #ReadableDBs
15311534
database_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+
15341549
IF EXISTS (SELECT * FROM sys.all_objects o WHERE o.name = 'dm_hadr_database_replica_states')
15351550
BEGIN
15361551
RAISERROR('Checking for Read intent databases to exclude',0,0) WITH NOWAIT;
@@ -1556,6 +1571,48 @@ FROM x
15561571
OPTION (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+
15591616
SET @OnlySqlHandles = LTRIM(RTRIM(@OnlySqlHandles)) ;
15601617
SET @OnlyQueryHashes = LTRIM(RTRIM(@OnlyQueryHashes)) ;
15611618
SET @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

Comments
 (0)