Deadlock Graph Capture with xml_deadlock_report
Overview
The xml_deadlock_report Extended Event fires when SQL Server detects a deadlock and chooses a victim. Each event contains the complete deadlock graph — an XML document listing all involved processes, resources (locks), and the victim selection details.
When a deadlock occurs, SQL Server:
- Detects the cyclic dependency via the lock monitor
- Selects a victim (typically the session with the lowest deadlock priority or least cost)
- Rolls back the victim transaction
- Fires the
xml_deadlock_reportevent
The event contains the full deadlock graph XML, which can be queried to identify contention patterns, problematic queries, and lock acquisition order.
Method 1: Querying Deadlocks from system_health
The system_health session automatically captures xml_deadlock_report events in its ring buffer. This is the quickest way to investigate recent deadlocks:
-- Extract deadlock graphs from the system_health ring buffer
-- Returns the most recent deadlock events with parsed process/resource details
SELECT deadlock.value('(event/@timestamp)[1]', 'datetime2') AS deadlock_time,
deadlock.query('event/data/value/deadlock') AS deadlock_graph_xml,
deadlock.value('(event/data[@name="database_name"]/value)[1]', 'nvarchar(256)') AS database_name
FROM (
SELECT CAST(target_data AS XML) AS session_target
FROM sys.dm_xe_session_targets
WHERE event_session_address = (
SELECT [address] FROM sys.dm_xe_sessions WHERE name = 'system_health'
)
AND target_name = 'ring_buffer'
) AS sh
CROSS APPLY sh.session_target.nodes('/RingBufferTarget/event[@name="xml_deadlock_report"]') AS d(deadlock)
ORDER BY deadlock_time DESC;
Parsing the Deadlock Graph into Process-Level Rows
-- Extract individual processes involved in each deadlock
WITH DeadlockEvents AS (
SELECT d.value('(event/@timestamp)[1]', 'datetime2') AS deadlock_time,
d.query('event/data/value/deadlock') AS graph
FROM (
SELECT CAST(target_data AS XML) AS target_data
FROM sys.dm_xe_session_targets
WHERE event_session_address = (
SELECT [address] FROM sys.dm_xe_sessions WHERE name = 'system_health'
)
AND target_name = 'ring_buffer'
) AS sh
CROSS APPLY sh.target_data.nodes('/RingBufferTarget/event[@name="xml_deadlock_report"]') AS d(event)
)
SELECT deadlock_time,
p.value('@id', 'varchar(50)') AS process_id,
p.value('@spid', 'int') AS spid,
p.value('@ecid', 'int') AS ecid,
p.value('@transactionname', 'varchar(100)') AS transaction_name,
p.value('@lasttranstarted', 'datetime2') AS transaction_start_time,
p.value('@lockmode', 'varchar(20)') AS lock_mode,
p.value('@clientapp', 'varchar(256)') AS client_application,
p.value('@hostname', 'varchar(128)') AS host_name,
p.value('@loginname', 'varchar(128)') AS login_name,
p.value('@isolationlevel', 'varchar(20)') AS isolation_level,
p.value('(inputbuf)[1]', 'nvarchar(max)') AS input_buffer
FROM DeadlockEvents
CROSS APPLY graph.nodes('//deadlock/process-list/process') AS p(process)
ORDER BY deadlock_time DESC, spid;
Method 2: Dedicated Deadlock Capture Session
For environments with frequent deadlocks, create a dedicated XEvent session with file-based output for historical analysis:
-- Create a dedicated deadlock capture session with file target
CREATE EVENT SESSION [deadlock_capture]
ON SERVER
ADD EVENT sqlserver.xml_deadlock_report (
ACTION (
sqlserver.database_name,
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.username,
sqlserver.session_id
)
WHERE (
[sqlserver].[database_name] = N'YourDatabaseName'
OR [sqlserver].[database_name] IS NULL
)
)
ADD TARGET package0.event_file (
SET filename = N'D:\XELogs\deadlock_capture.xel',
max_file_size = 50,
max_rollover_files = 5
)
WITH (
MAX_MEMORY = 2048 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 15 SECONDS,
MAX_EVENT_SIZE = 0 KB,
MEMORY_PARTITION_MODE = NONE,
TRACK_CAUSALITY = ON,
STARTUP_STATE = ON
);
GO
-- Start the session
ALTER EVENT SESSION [deadlock_capture] ON SERVER STATE = START;
GO
Querying the File-Based Deadlock Archive
-- Read deadlock events from the file target and parse deadlock graphs
WITH xevents AS (
SELECT event_data AS deadlock_event
FROM sys.fn_xe_file_target_read_file (
N'D:\XELogs\deadlock_capture*.xel',
N'D:\XELogs\deadlock_capture*.xem',
NULL,
NULL
)
)
SELECT event_data.value('(event/@timestamp)[1]', 'datetime2') AS deadlock_time,
event_data.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(256)') AS database_name,
event_data.value('(event/action[@name="client_app_name"]/value)[1]', 'nvarchar(256)') AS application_name,
event_data.query('event/data/value/deadlock') AS deadlock_graph
FROM xevents
ORDER BY deadlock_time DESC;
Enabling the Deadlock Trace Flag 1222
For backward compatibility or supplementing XEvent capture, enable trace flag 1222 to write deadlock graphs to the SQL Server error log:
-- Enable deadlock trace flag globally (requires restart or -T startup parameter)
DBCC TRACEON(1222, -1);
GO
-- Verify the trace flag is active
DBCC TRACESTATUS(1222);
GO
After enabling TF 1222, deadlock graphs appear in the error log. Use sp_readerrorlog to extract them:
-- Read deadlock graphs from the error log (TF 1222 must be enabled)
EXEC sp_readerrorlog 0, 1, 'deadlock';
Deadlock Graph XML Structure Reference
Understanding the deadlock graph XML enables programmatic analysis:
<deadlock>
<victim-list>
<victimProcess id="processXXXXXXX" />
</victim-list>
<process-list>
<process id="processXXXXXXX" spid="NN" ecid="0"
priority="0" logused="0" waitresource="KEY:."
waittime="XXXX" schedulerid="N" kpid="NNNN"
status="suspended". >
<executionStack>
<frame procname="dbname.schema.procname" line="NN" sqlhandle="." />
</executionStack>
<inputbuf>
/* Full T-SQL batch text */
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="NNNNNNNNNN" dbid="N" objectname="db.schema.table"
indexname="index_name" id="lockXXXXXXX" mode="X"
associatedObjectId="NNNNNNNNNN">
<owner-list>
<owner id="processXXXXXXX" mode="X" />
</owner-list>
<waiter-list>
<waiter id="processXXXXXXX" mode="S" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
Key Elements
| Element | Description |
|---|---|
<victim-list> | Identifies which session was chosen as the deadlock victim |
<process-list> | All sessions involved in the deadlock cycle |
<executionStack> | Call stack showing which query each session was executing |
<inputbuf> | Full T-SQL batch text for each process |
<resource-list> | Lock resources in contention — keylocks, RID locks, page locks, object locks |
<owner-list> | Sessions currently holding the lock |
<waiter-list> | Sessions waiting to acquire the lock |
Best Practices
- Always capture
xml_deadlock_reportproactively rather than reactively — deadlock graphs are useless after the fact if you do not have them captured. - Use file targets for persistent deadlock history — the system_health ring buffer overwrites old events.
- Filter by database name in the WHERE clause of your event session to avoid capturing irrelevant deadlocks.
- Set
max_rollover_filesappropriately — deadlock-heavy workloads can generate hundreds of MB of XEL files per day. - Correlate deadlock timing with application error logs to identify the precise impact on end users.
- Analyze the
inputbufandexecutionStackto pinpoint the exact queries and stored procedures causing contention, then optimize indexing or query patterns.