Skip to content

Commit 412a293

Browse files
authored
Merge pull request #2381 from erikdarlingdata/dev
Fix sp_BlitzLock heap identification
2 parents 359e705 + 567e199 commit 412a293

File tree

2 files changed

+137
-11
lines changed

2 files changed

+137
-11
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

sp_BlitzLock.sql

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ You need to use an Azure storage account, and the path has to look like this: ht
421421
w.l.value('@id', 'NVARCHAR(256)') AS waiter_id,
422422
w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode,
423423
o.l.value('@id', 'NVARCHAR(256)') AS owner_id,
424-
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode
424+
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode,
425+
N'OBJECT' AS lock_type
425426
INTO #deadlock_owner_waiter
426427
FROM (
427428
SELECT dr.event_date,
@@ -453,7 +454,8 @@ You need to use an Azure storage account, and the path has to look like this: ht
453454
w.l.value('@id', 'NVARCHAR(256)') AS waiter_id,
454455
w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode,
455456
o.l.value('@id', 'NVARCHAR(256)') AS owner_id,
456-
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode
457+
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode,
458+
N'PAGE' AS lock_type
457459
FROM (
458460
SELECT dr.event_date,
459461
ca.dr.value('@dbid', 'BIGINT') AS database_id,
@@ -482,7 +484,8 @@ You need to use an Azure storage account, and the path has to look like this: ht
482484
w.l.value('@id', 'NVARCHAR(256)') AS waiter_id,
483485
w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode,
484486
o.l.value('@id', 'NVARCHAR(256)') AS owner_id,
485-
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode
487+
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode,
488+
N'KEY' AS lock_type
486489
FROM (
487490
SELECT dr.event_date,
488491
ca.dr.value('@dbid', 'BIGINT') AS database_id,
@@ -511,7 +514,8 @@ You need to use an Azure storage account, and the path has to look like this: ht
511514
w.l.value('@id', 'NVARCHAR(256)') AS waiter_id,
512515
w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode,
513516
o.l.value('@id', 'NVARCHAR(256)') AS owner_id,
514-
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode
517+
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode,
518+
N'RID' AS lock_type
515519
FROM (
516520
SELECT dr.event_date,
517521
ca.dr.value('@dbid', 'BIGINT') AS database_id,
@@ -540,7 +544,8 @@ You need to use an Azure storage account, and the path has to look like this: ht
540544
w.l.value('@id', 'NVARCHAR(256)') AS waiter_id,
541545
w.l.value('@mode', 'NVARCHAR(256)') AS waiter_mode,
542546
o.l.value('@id', 'NVARCHAR(256)') AS owner_id,
543-
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode
547+
o.l.value('@mode', 'NVARCHAR(256)') AS owner_mode,
548+
N'ROWGROUP' AS lock_type
544549
FROM (
545550
SELECT dr.event_date,
546551
ca.dr.value('@dbid', 'BIGINT') AS database_id,
@@ -559,7 +564,7 @@ You need to use an Azure storage account, and the path has to look like this: ht
559564
SET d.index_name = d.object_name
560565
+ '.HEAP'
561566
FROM #deadlock_owner_waiter AS d
562-
WHERE index_name IS NULL
567+
WHERE lock_type IN (N'HEAP', N'RID')
563568
OPTION(RECOMPILE);
564569

565570
/*Parse parallel deadlocks*/
@@ -786,10 +791,32 @@ You need to use an Azure storage account, and the path has to look like this: ht
786791
AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
787792
AND (dow.event_date < @EndDate OR @EndDate IS NULL)
788793
AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
789-
AND dow.index_name IS NOT NULL
794+
AND dow.lock_type NOT IN (N'HEAP', N'RID')
790795
GROUP BY DB_NAME(dow.database_id), dow.index_name
791796
OPTION ( RECOMPILE );
792797

798+
799+
/*Check 2 continuation, number of locks per heap*/
800+
SET @d = CONVERT(VARCHAR(40), GETDATE(), 109);
801+
RAISERROR('Check 2 heaps %s', 0, 1, @d) WITH NOWAIT;
802+
INSERT #deadlock_findings WITH (TABLOCKX)
803+
( check_id, database_name, object_name, finding_group, finding )
804+
SELECT 2 AS check_id,
805+
ISNULL(DB_NAME(dow.database_id), 'UNKNOWN') AS database_name,
806+
dow.index_name AS index_name,
807+
'Total heap deadlocks' AS finding_group,
808+
'This heap was involved in '
809+
+ CONVERT(NVARCHAR(20), COUNT_BIG(DISTINCT dow.event_date))
810+
+ ' deadlock(s).'
811+
FROM #deadlock_owner_waiter AS dow
812+
WHERE 1 = 1
813+
AND (DB_NAME(dow.database_id) = @DatabaseName OR @DatabaseName IS NULL)
814+
AND (dow.event_date >= @StartDate OR @StartDate IS NULL)
815+
AND (dow.event_date < @EndDate OR @EndDate IS NULL)
816+
AND (dow.object_name = @ObjectName OR @ObjectName IS NULL)
817+
AND dow.lock_type IN (N'HEAP', N'RID')
818+
GROUP BY DB_NAME(dow.database_id), dow.index_name
819+
OPTION ( RECOMPILE );
793820

794821

795822
/*Check 3 looks for Serializable locking*/

0 commit comments

Comments
 (0)