Blog Post

Blocked Process Report: How to Get More Value

,

Blocking in SQL Server is not an uncommon phenomena. Many times it is rather trivial. Sometimes, it extends beyond the realm of trivial and needs to be addressed.

How do you know when a blocked process has gone beyond the realm of trivial? What can you do to find long held blocks in SQL Server? The answer to that is actually not that simple. Why? Because there are so many methods that can help uncover blocking issues that it would be difficult to address in a single article.

However, there is a combination of tools I wish to discuss in this article that can be added to your toolbelt. These tools will help address the varying levels of blocking issues within your database environment. What are the tools? Naturally one of the tools would be Extended Events (XEvents). And it just so happens that the other tool is somewhat new to me – it is the Blocked Process Report.

Yeah yeah yeah, I said it is new to me. Truth is I haven’t used it previously because I have always used a custom built solution. That isn’t to say I wasn’t aware of the option – just hadn’t employed it. Why now? Well, to be honest it is because of the ease of use with XEvents.

For the sake of posterity, I am also adding this to the MASSIVE collection of Extended Events articles.

More Value from the Blocked Process Report

One of the big problems, I fear, with the blocked process report is that it is not easily consumed. It is somewhat kludgy and hard to read – unless you are really familiar with that kind of output. In addition, you have to twist a couple of knobs to make it work. Tweak these knobs too far and you can cause more harm than good.

Behind the scenes, the blocked process report is controlled by the deadlock detector in SQL Server. By default, the deadlock detector wakes every 5 seconds and checks for “problems”. To get the blocked process report to utilize this resource, you must configure a setting. This setting is named “blocked process threshold (s)” and is an advanced option. It is easy to adjust with a script such as the following.

SELECT *
FROM sys.configurations c
WHERE c.[name] LIKE 'block%';
IF EXISTS (SELECT *
FROM sys.configurations c
WHERE c.[name] LIKE 'block%'
AND (c.value = 0
OR c.value_in_use = 0))
BEGIN
EXECUTE sp_configure 'show advanced options', 1;
RECONFIGURE WITH OVERRIDE;
EXECUTE sp_configure 'blocked', 20; --blocked process threshold (s)
RECONFIGURE WITH OVERRIDE;
END
GO
SELECT *
FROM sys.configurations c
WHERE c.[name] LIKE 'block%';

The previous script will evaluate the “blocked process threshold (3)” setting. If that setting is set to 0, then it will be updated (with override) to 20. This means that now the blocked process report will tap into the deadlock detector every 20 seconds and report on any blocking.

Great! Where do we find that data? That is where the power of XEvents comes into play!

XEvents Power and Blocked Processes

As previously mentioned, consuming the blocked process report is a bit of a problem. Sure, one could have used a server side trace to monitor the blocked_process_report event. That, honestly, was less than ideal. With the presence of XEvents, we have a true power tool and a remarkably ideal tool to consume and monitor the event. In the following script, I have provided the means to create an XE Session that will capture the blocked_process_report event as well as parse it into human friendly format.

Note, I have included the trace_print event in the provided session. This is to demonstrate the power of the parse script. If you have multiple events being tracked in the same session, the parse script will just bypass all of those events and only look at the blocked_process_report event.

USE master;
GO
DECLARE @XESession NVARCHAR(MAX) = 'Blocking'
  , @DropXESQLVARCHAR(256);
-- Create the Event Session
IF EXISTS ( SELECT *
FROM sys.server_event_sessions ses
WHERE ses.[name] = @XESession )
BEGIN
SET @DropXESQL = 'DROP EVENT SESSION ' + @XESession + '
ON SERVER;'
EXECUTE (@DropXESQL)
END;
CREATE EVENT SESSION [Blocking]
ON SERVER
ADD EVENT sqlserver.blocked_process_report
(ACTION (
 package0.event_sequence
   , sqlserver.client_app_name
   , sqlserver.client_hostname
   , sqlserver.database_name
   , sqlserver.plan_handle
   , sqlserver.session_id
   , sqlserver.sql_text
 )
)
  , ADD EVENT sqlserver.trace_print
(ACTION (
 package0.event_sequence
   , sqlserver.client_app_name
   , sqlserver.client_hostname
   , sqlserver.database_name
   , sqlserver.plan_handle
   , sqlserver.session_id
   , sqlserver.sql_text
 )
)
ADD TARGET package0.event_file
(SET filename = N'C:DatabaseXEBlocking.xel')
WITH (
MAX_MEMORY = 4096KB
  , EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS
  , MAX_DISPATCH_LATENCY = 30 SECONDS
  , MAX_EVENT_SIZE = 0KB
  , MEMORY_PARTITION_MODE = NONE
  , TRACK_CAUSALITY = ON
  , STARTUP_STATE = OFF
);
GO
IF OBJECT_ID('tempdb..#xmlprocess') IS NOT NULL
BEGIN
DROP TABLE #xmlprocess;
END;
IF OBJECT_ID('tempdb..#ReportsXML') IS NOT NULL
BEGIN
DROP TABLE #ReportsXML;
END;
SET NOCOUNT ON;
IF NOT EXISTS (
SELECT*
FROMsys.server_event_sessions  es
INNER JOIN sys.server_event_session_targets est
ON es.event_session_id = est.event_session_id
WHEREest.name IN ( 'event_file' )
AND es.name = @XESession
)
RAISERROR(
'Warning: The extended event session you supplied does not exist or does not have an "event_file" or "ring_buffer" target.'
  , 10
  , 1
);
CREATE TABLE #ReportsXML
(
MonitorLoopID NVARCHAR(100) NOT NULL
  , endTime DATETIME NULL
  , blocking_spid INT NOT NULL
  , blocking_ecid INT NOT NULL
  , blocked_spid INT NOT NULL
  , blocked_ecid INT NOT NULL
  , blocked_hierarchy_string AS CAST(blocked_spid AS VARCHAR(20)) + '.' + CAST(blocked_ecid AS VARCHAR(20)) + '/'
  , blocking_hierarchy_string AS CAST(blocking_spid AS VARCHAR(20)) + '.' + CAST(blocking_ecid AS VARCHAR(20)) + '/'
  , BlockedProcessReportXML XML NOT NULL
  ,
  PRIMARY KEY CLUSTERED (
  MonitorLoopID
, blocked_spid
, blocked_ecid
  )
  ,
  UNIQUE NONCLUSTERED (
  MonitorLoopID
, blocking_spid
, blocking_ecid
, blocked_spid
, blocked_ecid
  )
);
BEGIN
DECLARE @SessionType NVARCHAR(MAX);
DECLARE @SessionId INT;
DECLARE @SessionTargetId INT;
DECLARE @FilenamePattern NVARCHAR(MAX);
SELECTTOP (1)
@SessionType = est.name
  , @SessionId = est.event_session_id
  , @SessionTargetId = est.target_id
FROMsys.server_event_sessions  es
INNER JOIN sys.server_event_session_targets est
ON es.event_session_id = est.event_session_id
WHEREest.name IN ( 'event_file')
AND es.name = @XESession;
IF (@SessionType = 'event_file')
BEGIN
SELECT@FilenamePattern = REPLACE(CAST([value] AS NVARCHAR(MAX)), '.xel', '*xel')
FROMsys.server_event_session_fields esf
WHEREesf.event_session_id = @SessionId
AND esf.[object_id] = @SessionTargetId
AND esf.[name] = 'filename';
IF (@FilenamePattern NOT LIKE '%xel')
SET @FilenamePattern += '*xel';
/* parse event data */SELECT CAST ([t2].[event_data] AS XML) AS event_data, t2.file_offset,t2.file_name, cte1.event_session_id
INTO #xmlprocess
FROM ( SELECT CASE
WHEN CAST(sesf.[value] AS NVARCHAR(128))  NOT LIKE '%xel'
THEN REPLACE(CONVERT(NVARCHAR(128),CONVERT(NVARCHAR(128),sesf.[value]) + '.xel'),'.xel','*.xel') 
ELSE REPLACE(CONVERT(NVARCHAR(128),sesf.[value]),'.xel','*.xel') 
END AS targetvalue
, ses.event_session_id
FROM sys.server_event_sessions ses
INNER JOIN sys.server_event_session_fields sesf
ON ses.event_session_id = sesf.event_session_id
WHERE sesf.[name] = 'filename'
AND ses.[name] = @XESession
) cte1
OUTER APPLY sys.fn_xe_file_target_read_file(cte1.targetvalue,NULL, NULL, NULL) t2
WHERE t2.event_data LIKE '<event name="blocked%';
INSERT INTO#ReportsXML
(
blocked_ecid
  , blocked_spid
  , blocking_ecid
  , blocking_spid
  , MonitorLoopID
  , BlockedProcessReportXML
  , endTime
)
SELECTbpShredded.blocked_ecid
  , bpShredded.blocked_spid
  , bpShredded.blocking_ecid
  , bpShredded.blocking_spid
  , COALESCE(
bpShredded.MonitorLoopID, CONVERT(NVARCHAR(100), bpReports.EventDate, 120), CAST(NEWID() AS NVARCHAR(100))
)
  , bpReports.BlockedProcessReportXML
  , bpReports.EventDate
FROM#xmlprocess AS x
CROSS APPLY (
SELECTx.[event_data].value('(event/@timestamp)[1]', 'datetime')  AS EventDate
  , x.[event_data].query('//event/data/value/blocked-process-report') AS BlockedProcessReportXML
) AS bpReports
CROSS APPLY (
SELECTMonitorLoopID  = BlockedProcessReportXML.value('(//@monitorLoop)[1]', 'nvarchar(100)')
  , blocked_spid  = BlockedProcessReportXML.value(
'(/blocked-process-report/blocked-process/process/@spid)[1]'
  , 'int'
)
  , blocked_ecid  = BlockedProcessReportXML.value(
'(/blocked-process-report/blocked-process/process/@ecid)[1]'
  , 'int'
)
  , blocking_spid = BlockedProcessReportXML.value(
'(/blocked-process-report/blocking-process/process/@spid)[1]'
  , 'int'
)
  , blocking_ecid = BlockedProcessReportXML.value(
'(/blocked-process-report/blocking-process/process/@ecid)[1]'
  , 'int'
)
) AS bpShredded
WHEREbpShredded.blocking_spid IS NOT NULL
AND bpShredded.blocked_spid IS NOT NULL;
END;
END;
WITH HeadBlockers AS (
 SELECT blocking_spid
  , blocking_ecid
  , MonitorLoopID
  , blocking_hierarchy_string
 FROM#ReportsXML
 EXCEPT
 SELECT blocked_spid
  , blocked_ecid
  , MonitorLoopID
  , blocked_hierarchy_string
 FROM#ReportsXML
 )
   , Hierarchy AS (
  SELECTMonitorLoopID
  , blocking_spid  AS spid
  , blocking_ecid  AS ecid
  , CAST('/' + blocking_hierarchy_string AS VARCHAR(MAX)) AS chain
  , 0  AS level
  FROMHeadBlockers
  UNION ALL
  SELECTirx.MonitorLoopID
  , irx.blocked_spid
  , irx.blocked_ecid
  , CAST(h.chain + irx.blocked_hierarchy_string AS VARCHAR(MAX))
  , h.level + 1
  FROM#ReportsXML irx
  INNER JOIN Hierarchy h
  ON irx.MonitorLoopID = h.MonitorLoopID
  ANDirx.blocking_spid = h.spid
  ANDirx.blocking_ecid = h.ecid
  )
SELECTISNULL(CONVERT(NVARCHAR(30), irx.endTime, 120), '') AS TraceTime
,CASE WHEN ISNULL(CONVERT(NVARCHAR(30), irx.endTime, 120), '') = ''
THEN 'Lead Blocker'
ELSE 'Blocked Process'
END AS BlockingParticipationType
, SPACE(4 * h.[level]) + CAST(h.spid AS VARCHAR(20)) + CASE h.ecid
   WHEN 0
   THEN ''
   ELSE '(' + CAST(h.ecid AS VARCHAR(20)) + ')'
   ENDAS BlockingTree
  , irx.BlockedProcessReportXML
FROMHierarchy  h
LEFT OUTER JOIN #ReportsXML irx
ON irx.MonitorLoopID = h.MonitorLoopID
AND irx.blocked_spid = h.spid
AND irx.blocked_ecid = h.ecid
ORDER BY h.MonitorLoopID
   , h.chain;
DROP TABLE #ReportsXML;
DROP TABLE #xmlprocess;

The parse script is mostly attributable to Michael J. Swart. He provided the bulk of the work that I used to tune and make go faster (roughly 4x faster). You can find his original work here.

Blocked Process Output

If I parse a running XE Session, looking at the blocked_process_report, I could see something like the following.

blocked process outputAs you can see, the output lets us see a rudimentary blocking tree and helps to identify the lead blocker. In addition, this blocked process output also produces an XML value that is clickable so we can see more information about the blocked process. Here is a sample of one of the XML clickable values from a parse of this session I have running.

This is where the power resides. The ease of XEvents coupled with the blocked process report, means minimal code and easy access to see blocking sessions. Not just current blocking sessions, but now I have easily logged the blocking sessions to be reviewed at a future date. In short, this provides the means for historical review as well as real time blocking info. Even better is that I can make a tweak here or there and make it so somebody can be paged when certain thresholds are met. All based upon this simple XE Session. That is true power! That is real value for the blocked process report!

Put a bow on it

By combining the raw power of the Extended Events engine in SQL Server with the Blocked Process Report, we are able to achieve real value in blocking monitoring. This can be achieved without excessive expense, expertise, or effort!

Go ahead, have a try at it!

Interested in learning more deep technical information? Check these out!

Want to learn more about your indexes? Try this index maintenance article or this index size article.

This is the third article in the 2021 “12 Days of Christmas” series. For the full list of articles, please visit this page.

The post Blocked Process Report: How to Get More Value first appeared on SQL RNNR.

Original post (opens in new tab)
View comments in original post (opens in new tab)

Rate

You rated this post out of 5. Change rating

Share

Share

Rate

You rated this post out of 5. Change rating