@@ -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 ;
211215END ; /* 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