Skip to content

Commit d3fb163

Browse files
Update sp_PressureDetector.sql
Adds a minimal blocking troubleshooter facility to this as a standalone query.
1 parent c5c8b74 commit d3fb163

1 file changed

Lines changed: 315 additions & 0 deletions

File tree

sp_PressureDetector/sp_PressureDetector.sql

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ ALTER PROCEDURE
6363
@log_table_name_prefix sysname = 'PressureDetector', /*prefix for all logging tables*/
6464
@log_retention_days integer = 30, /*Number of days to keep logs, 0 = keep indefinitely*/
6565
@help bit = 0, /*how you got here*/
66+
@troubleshoot_blocking bit = 0, /*show blocking chains instead of pressure analysis*/
6667
@debug bit = 0, /*prints dynamic sql, displays parameter and variable values, and table contents*/
6768
@version varchar(5) = NULL OUTPUT, /*OUTPUT; for support*/
6869
@version_date datetime = NULL OUTPUT /*OUTPUT; for support*/
@@ -122,6 +123,7 @@ BEGIN
122123
WHEN N'@log_table_name_prefix' THEN N'prefix for all logging tables'
123124
WHEN N'@log_retention_days' THEN N'how many days of data to retain'
124125
WHEN N'@help' THEN N'how you got here'
126+
WHEN N'@troubleshoot_blocking' THEN N'show blocking chains instead of pressure analysis'
125127
WHEN N'@debug' THEN N'prints dynamic sql, displays parameter and variable values, and table contents'
126128
WHEN N'@version' THEN N'OUTPUT; for support'
127129
WHEN N'@version_date' THEN N'OUTPUT; for support'
@@ -143,6 +145,7 @@ BEGIN
143145
WHEN N'@log_table_name_prefix' THEN N'any valid identifier'
144146
WHEN N'@log_retention_days' THEN N'a positive integer'
145147
WHEN N'@help' THEN N'0 or 1'
148+
WHEN N'@troubleshoot_blocking' THEN N'0 or 1'
146149
WHEN N'@debug' THEN N'0 or 1'
147150
WHEN N'@version' THEN N'none'
148151
WHEN N'@version_date' THEN N'none'
@@ -164,6 +167,7 @@ BEGIN
164167
WHEN N'@log_table_name_prefix' THEN N'PressureDetector'
165168
WHEN N'@log_retention_days' THEN N'30'
166169
WHEN N'@help' THEN N'0'
170+
WHEN N'@troubleshoot_blocking' THEN N'0'
167171
WHEN N'@debug' THEN N'0'
168172
WHEN N'@version' THEN N'none; OUTPUT'
169173
WHEN N'@version_date' THEN N'none; OUTPUT'
@@ -210,6 +214,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
210214
RETURN;
211215
END; /*End help section*/
212216

217+
/*
218+
If @troubleshoot_blocking = 1, skip all other analysis and go directly to blocking analysis
219+
*/
220+
IF @troubleshoot_blocking = 1
221+
BEGIN
222+
GOTO troubleshoot_blocking;
223+
END;
224+
213225
/*
214226
Fix parameters and check the values, etc.
215227
*/
@@ -4025,6 +4037,309 @@ OPTION(MAXDOP 1, RECOMPILE);',
40254037
GOTO DO_OVER;
40264038
END;
40274039

4040+
troubleshoot_blocking:
4041+
IF @troubleshoot_blocking = 1
4042+
BEGIN
4043+
/*
4044+
Blocking chain analysis - mimics sp_WhoIsActive @find_block_leaders = 1
4045+
Uses sys.sysprocesses to find both active and idle blockers
4046+
Uses recursive CTE to walk blocking chains
4047+
*/
4048+
4049+
/*
4050+
Table variable to hold lead blockers (anchor rows)
4051+
This improves performance by materializing the anchor set once
4052+
*/
4053+
DECLARE
4054+
@lead_blockers table
4055+
(
4056+
session_id smallint NOT NULL
4057+
PRIMARY KEY WITH (IGNORE_DUP_KEY = ON),
4058+
blocking_session_id smallint NOT NULL,
4059+
blocking_chain nvarchar(4000) NOT NULL
4060+
);
4061+
4062+
/*
4063+
Find lead blockers: sessions that are blocking others
4064+
but not blocked themselves (includes idle sessions with open transactions)
4065+
*/
4066+
INSERT
4067+
@lead_blockers
4068+
(
4069+
session_id,
4070+
blocking_session_id,
4071+
blocking_chain
4072+
)
4073+
SELECT
4074+
session_id = sp.spid,
4075+
blocking_session_id = sp.blocked,
4076+
blocking_chain =
4077+
CONVERT
4078+
(
4079+
nvarchar(4000),
4080+
CONVERT(nvarchar(20), sp.spid) + N' lead blocker'
4081+
)
4082+
FROM sys.sysprocesses AS sp
4083+
WHERE sp.blocked = 0
4084+
AND EXISTS
4085+
(
4086+
SELECT
4087+
1/0
4088+
FROM sys.sysprocesses AS sp2
4089+
WHERE sp2.blocked = sp.spid
4090+
);
4091+
4092+
/*
4093+
Recursive CTE to walk blocking chains
4094+
Anchor: lead blockers from table variable
4095+
Recursive: sessions blocked by current level
4096+
*/
4097+
WITH
4098+
blockers
4099+
(
4100+
session_id,
4101+
blocking_session_id,
4102+
blocking_level,
4103+
top_level_blocker,
4104+
blocking_chain,
4105+
visited_path
4106+
) AS
4107+
(
4108+
/*
4109+
Anchor: Lead blockers from table variable
4110+
*/
4111+
SELECT
4112+
session_id = lb.session_id,
4113+
blocking_session_id = lb.blocking_session_id,
4114+
blocking_level = 0,
4115+
top_level_blocker = lb.session_id,
4116+
blocking_chain = lb.blocking_chain,
4117+
visited_path =
4118+
CONVERT
4119+
(
4120+
nvarchar(4000),
4121+
N'.' + CONVERT(nvarchar(20), lb.session_id) + N'.'
4122+
)
4123+
FROM @lead_blockers AS lb
4124+
4125+
UNION ALL
4126+
4127+
/*
4128+
Recursive: Walk down the blocking chain
4129+
*/
4130+
SELECT
4131+
session_id = sp.spid,
4132+
blocking_session_id = sp.blocked,
4133+
blocking_level = b.blocking_level + 1,
4134+
top_level_blocker = b.top_level_blocker,
4135+
blocking_chain =
4136+
CONVERT
4137+
(
4138+
nvarchar(4000),
4139+
REPLICATE(N' > ', b.blocking_level + 1) +
4140+
CONVERT(nvarchar(20), sp.blocked) +
4141+
N' blocking ' +
4142+
CONVERT(nvarchar(20), sp.spid)
4143+
),
4144+
visited_path =
4145+
CONVERT
4146+
(
4147+
nvarchar(4000),
4148+
b.visited_path + CONVERT(nvarchar(20), sp.spid) + N'.'
4149+
)
4150+
FROM blockers AS b
4151+
JOIN sys.sysprocesses AS sp
4152+
ON sp.blocked = b.session_id
4153+
WHERE b.visited_path NOT LIKE
4154+
N'%.' + CONVERT(nvarchar(20), sp.spid) + N'.%'
4155+
),
4156+
blocking_info
4157+
(
4158+
session_id,
4159+
blocking_session_id,
4160+
blocking_level,
4161+
top_level_blocker,
4162+
blocking_chain,
4163+
blocked_session_count,
4164+
last_batch,
4165+
status,
4166+
wait_type,
4167+
wait_time,
4168+
wait_resource,
4169+
cpu_time,
4170+
physical_io,
4171+
memusage,
4172+
open_transaction_count,
4173+
database_name,
4174+
command,
4175+
sql_handle,
4176+
statement_start_offset,
4177+
statement_end_offset,
4178+
login_name,
4179+
host_name,
4180+
program_name,
4181+
login_time
4182+
) AS
4183+
(
4184+
/*
4185+
Join blocking chain results to sysprocesses for session details
4186+
SQL text and query plans are NOT retrieved here - done in final SELECT
4187+
*/
4188+
SELECT
4189+
b.session_id,
4190+
b.blocking_session_id,
4191+
b.blocking_level,
4192+
b.top_level_blocker,
4193+
b.blocking_chain,
4194+
blocked_session_count =
4195+
(
4196+
SELECT
4197+
COUNT_BIG(*)
4198+
FROM blockers AS b2
4199+
WHERE b2.visited_path LIKE
4200+
N'%.' + CONVERT(nvarchar(20), b.session_id) + N'.%'
4201+
AND b2.session_id <> b.session_id
4202+
),
4203+
sp.last_batch,
4204+
sp.status,
4205+
wait_type = sp.lastwaittype,
4206+
wait_time = sp.waittime,
4207+
wait_resource = sp.waitresource,
4208+
cpu_time = sp.cpu,
4209+
physical_io = sp.physical_io,
4210+
sp.memusage,
4211+
open_transaction_count = sp.open_tran,
4212+
database_name = DB_NAME(sp.dbid),
4213+
command = sp.cmd,
4214+
sp.sql_handle,
4215+
statement_start_offset = sp.stmt_start,
4216+
statement_end_offset = sp.stmt_end,
4217+
login_name = sp.loginame,
4218+
host_name = sp.hostname,
4219+
program_name = sp.program_name,
4220+
sp.login_time
4221+
FROM blockers AS b
4222+
JOIN sys.sysprocesses AS sp
4223+
ON sp.spid = b.session_id
4224+
)
4225+
/*
4226+
Final SELECT: retrieve SQL text and query plans last for performance
4227+
Column order matches sp_WhoIsActive where possible
4228+
*/
4229+
SELECT
4230+
[dd hh:mm:ss.mss] =
4231+
CASE
4232+
WHEN e.elapsed_time_ms < 0
4233+
THEN RIGHT(REPLICATE('0', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
4234+
' ' +
4235+
RIGHT(CONVERT(varchar(30), DATEADD(SECOND, (-1 * e.elapsed_time_ms), 0), 120), 9) +
4236+
'.000'
4237+
ELSE RIGHT(REPLICATE('0', 2) +
4238+
CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
4239+
' ' +
4240+
RIGHT(CONVERT(varchar(30), DATEADD(SECOND, e.elapsed_time_ms / 1000, 0), 120), 9) +
4241+
'.' +
4242+
RIGHT('000' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
4243+
END,
4244+
bi.session_id,
4245+
blocking_session_id = NULLIF(bi.blocking_session_id, 0),
4246+
bi.blocking_level,
4247+
bi.top_level_blocker,
4248+
bi.blocking_chain,
4249+
bi.blocked_session_count,
4250+
query_text =
4251+
(
4252+
SELECT
4253+
[processing-instruction(query)] =
4254+
SUBSTRING
4255+
(
4256+
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
4257+
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
4258+
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
4259+
REPLACE(
4260+
dest.text COLLATE Latin1_General_BIN2,
4261+
NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
4262+
NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'),
4263+
NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N''),
4264+
N'<?', N'??'), N'?>', N'??'),
4265+
(bi.statement_start_offset / 2) + 1,
4266+
(
4267+
(
4268+
CASE bi.statement_end_offset
4269+
WHEN -1
4270+
THEN DATALENGTH(dest.text)
4271+
ELSE bi.statement_end_offset
4272+
END - bi.statement_start_offset
4273+
) / 2
4274+
) + 1
4275+
)
4276+
FOR XML PATH(''),
4277+
TYPE
4278+
),
4279+
query_plan =
4280+
CASE
4281+
WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
4282+
THEN TRY_CAST(deqp.query_plan AS xml)
4283+
WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
4284+
THEN
4285+
(
4286+
SELECT
4287+
[processing-instruction(query_plan)] =
4288+
N'-- ' + NCHAR(13) + NCHAR(10) +
4289+
N'-- This is a huge query plan.' + NCHAR(13) + NCHAR(10) +
4290+
N'-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR(13) + NCHAR(10) +
4291+
NCHAR(13) + NCHAR(10) +
4292+
REPLACE(deqp.query_plan, N'<RelOp', NCHAR(13) + NCHAR(10) + N'<RelOp') +
4293+
NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
4294+
FOR XML PATH(N''),
4295+
TYPE
4296+
)
4297+
END,
4298+
bi.login_name,
4299+
wait_time_ms = bi.wait_time,
4300+
wait_type = NULLIF(bi.wait_type, N'MISCELLANEOUS'),
4301+
bi.wait_resource,
4302+
reads = ISNULL(der.reads, 0),
4303+
writes = ISNULL(der.writes, 0),
4304+
physical_reads = ISNULL(der.logical_reads, bi.physical_io),
4305+
cpu_time = ISNULL(der.cpu_time, bi.cpu_time),
4306+
used_memory = ISNULL(der.granted_query_memory, bi.memusage),
4307+
bi.status,
4308+
bi.open_transaction_count,
4309+
bi.host_name,
4310+
bi.database_name,
4311+
bi.program_name,
4312+
bi.last_batch,
4313+
bi.login_time
4314+
FROM blocking_info AS bi
4315+
LEFT JOIN sys.dm_exec_requests AS der
4316+
ON der.session_id = bi.session_id
4317+
OUTER APPLY
4318+
(
4319+
SELECT
4320+
elapsed_time_ms =
4321+
CASE
4322+
WHEN DATEDIFF(HOUR, ISNULL(der.start_time, bi.last_batch), SYSDATETIME()) > 576
4323+
THEN DATEDIFF(SECOND, SYSDATETIME(), ISNULL(der.start_time, bi.last_batch))
4324+
ELSE DATEDIFF(MILLISECOND, ISNULL(der.start_time, bi.last_batch), SYSDATETIME())
4325+
END
4326+
) AS e
4327+
OUTER APPLY sys.dm_exec_sql_text(bi.sql_handle) AS dest
4328+
OUTER APPLY sys.dm_exec_text_query_plan
4329+
(
4330+
der.plan_handle,
4331+
der.statement_start_offset,
4332+
der.statement_end_offset
4333+
) AS deqp
4334+
ORDER BY
4335+
bi.top_level_blocker,
4336+
bi.blocking_level,
4337+
bi.session_id
4338+
OPTION(MAXRECURSION 0);
4339+
4340+
RETURN;
4341+
END; /*End troubleshoot_blocking*/
4342+
40284343
IF @debug = 1
40294344
BEGIN
40304345
SELECT

0 commit comments

Comments
 (0)