📊 SQL Server Extended Events: The Complete Monitoring Guide
Extended Events (XEvents) is SQL Server's modern, lightweight, and highly scalable event-handling system. It replaces SQL Trace and SQL Profiler with a more efficient, less intrusive monitoring framework. This comprehensive guide covers everything you need to master Extended Events for production monitoring.
🔧 Understanding Extended Events Architecture
Core Components
| Component | Description |
|---|---|
| Events | Points of interest (query completion, errors, waits) |
| Actions | Additional data to collect when event fires |
| Predicates | Filters to limit which events are captured |
| Targets | Destinations for event data (files, ring buffer) |
| Sessions | Container that ties events, actions, and targets together |
Extended Events vs SQL Trace
| Feature | SQL Trace | Extended Events |
|---|---|---|
| Overhead | 10-30% | 1-3% |
| Filtering | Column-based | Predicate expressions |
| Scalability | Limited | Highly scalable |
| Async processing | No | Yes |
| Memory management | Fixed buffers | Dynamic allocation |
| Future support | Deprecated | Active development |
🚀 Creating Extended Events Sessions
Basic Query Monitoring Session
-- Monitor slow queries (> 5 seconds)
CREATE EVENT SESSION [SlowQueries] ON SERVER
ADD EVENT sqlserver.sql_statement_completed
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name,
sqlserver.session_id,
sqlserver.sql_text,
sqlserver.username
)
WHERE
(
duration >= 5000000 -- 5 seconds in microseconds
)
),
ADD EVENT sqlserver.rpc_completed
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name,
sqlserver.session_id,
sqlserver.sql_text,
sqlserver.username
)
WHERE
(
duration >= 5000000
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\SlowQueries.xel',
max_file_size = 100, -- MB
max_rollover_files = 10
)
WITH
(
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 30 SECONDS,
STARTUP_STATE = ON
);
-- Start the session
ALTER EVENT SESSION [SlowQueries] ON SERVER STATE = START;
Error and Exception Monitoring
-- Capture all errors and exceptions
CREATE EVENT SESSION [ErrorMonitoring] ON SERVER
ADD EVENT sqlserver.error_reported
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name,
sqlserver.session_id,
sqlserver.sql_text,
sqlserver.tsql_stack,
sqlserver.username
)
WHERE
(
severity >= 11 -- Errors only, not informational
)
),
ADD EVENT sqlserver.exception_ring_buffer_recorded
(
ACTION
(
sqlserver.session_id,
sqlserver.sql_text
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\Errors.xel',
max_file_size = 50,
max_rollover_files = 20
),
ADD TARGET package0.ring_buffer
(
SET max_memory = 4096
)
WITH
(
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 5 SECONDS,
STARTUP_STATE = ON
);
ALTER EVENT SESSION [ErrorMonitoring] ON SERVER STATE = START;
Wait Statistics Monitoring
-- Capture wait events for specific wait types
CREATE EVENT SESSION [WaitAnalysis] ON SERVER
ADD EVENT sqlos.wait_completed
(
ACTION
(
sqlserver.client_app_name,
sqlserver.database_name,
sqlserver.plan_handle,
sqlserver.query_hash,
sqlserver.session_id,
sqlserver.sql_text
)
WHERE
(
-- Focus on significant waits
duration > 1000 -- > 1ms
AND
(
wait_type = 'LCK_M_X' OR
wait_type = 'LCK_M_U' OR
wait_type = 'LCK_M_S' OR
wait_type = 'PAGEIOLATCH_SH' OR
wait_type = 'PAGEIOLATCH_EX' OR
wait_type = 'WRITELOG' OR
wait_type = 'ASYNC_NETWORK_IO' OR
wait_type = 'CXPACKET'
)
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\Waits.xel',
max_file_size = 100,
max_rollover_files = 5
)
WITH
(
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 30 SECONDS,
STARTUP_STATE = OFF
);
💡 Common Monitoring Scenarios
Deadlock Monitoring
-- Comprehensive deadlock capture
CREATE EVENT SESSION [DeadlockMonitor] ON SERVER
ADD EVENT sqlserver.xml_deadlock_report
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name
)
),
ADD EVENT sqlserver.lock_deadlock
(
ACTION
(
sqlserver.client_app_name,
sqlserver.database_name,
sqlserver.session_id,
sqlserver.sql_text
)
),
ADD EVENT sqlserver.lock_deadlock_chain
(
ACTION
(
sqlserver.session_id,
sqlserver.sql_text
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\Deadlocks.xel',
max_file_size = 50,
max_rollover_files = 10
),
ADD TARGET package0.ring_buffer
(
SET max_memory = 4096
)
WITH
(
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = NO_EVENT_LOSS, -- Don't lose deadlock info
MAX_DISPATCH_LATENCY = 5 SECONDS,
STARTUP_STATE = ON
);
Login Auditing
-- Track all login attempts
CREATE EVENT SESSION [LoginAudit] ON SERVER
ADD EVENT sqlserver.login
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.server_principal_name
)
),
ADD EVENT sqlserver.logout
(
ACTION
(
sqlserver.session_id
)
),
ADD EVENT sqlserver.login_failed
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\LoginAudit.xel',
max_file_size = 100,
max_rollover_files = 30 -- 30 days retention
)
WITH
(
MAX_MEMORY = 2048 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 30 SECONDS,
STARTUP_STATE = ON
);
Query Plan Changes
-- Monitor query plan recompilations
CREATE EVENT SESSION [PlanChanges] ON SERVER
ADD EVENT sqlserver.sql_statement_recompile
(
ACTION
(
sqlserver.client_app_name,
sqlserver.database_name,
sqlserver.plan_handle,
sqlserver.query_hash,
sqlserver.sql_text
)
),
ADD EVENT sqlserver.query_post_compilation_showplan
(
ACTION
(
sqlserver.database_name,
sqlserver.query_hash,
sqlserver.sql_text
)
WHERE
(
duration > 1000000 -- Compilations > 1 second
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\PlanChanges.xel',
max_file_size = 100
)
WITH
(
MAX_MEMORY = 4096 KB,
MAX_DISPATCH_LATENCY = 30 SECONDS,
STARTUP_STATE = ON
);
📊 Reading Extended Events Data
Query Ring Buffer Target
-- Read from ring buffer
SELECT
event_data.value('(event/@name)[1]', 'nvarchar(100)') AS EventName,
event_data.value('(event/@timestamp)[1]', 'datetime2') AS EventTime,
event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') / 1000 AS Duration_ms,
event_data.value('(event/data[@name="cpu_time"]/value)[1]', 'bigint') / 1000 AS CPU_ms,
event_data.value('(event/data[@name="logical_reads"]/value)[1]', 'bigint') AS LogicalReads,
event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS SQLText,
event_data.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS DatabaseName,
event_data.value('(event/action[@name="username"]/value)[1]', 'nvarchar(128)') AS UserName
FROM
(
SELECT CAST(target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
INNER JOIN sys.dm_xe_sessions s ON st.event_session_address = s.address
WHERE s.name = 'SlowQueries'
AND st.target_name = 'ring_buffer'
) AS Data
CROSS APPLY TargetData.nodes('RingBufferTarget/event') AS XEventData(event_data)
ORDER BY EventTime DESC;
Query File Target
-- Read from event file
SELECT
event_data.value('(event/@name)[1]', 'nvarchar(100)') AS EventName,
event_data.value('(event/@timestamp)[1]', 'datetime2') AS EventTime,
event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') / 1000 AS Duration_ms,
event_data.value('(event/data[@name="cpu_time"]/value)[1]', 'bigint') / 1000 AS CPU_ms,
event_data.value('(event/data[@name="physical_reads"]/value)[1]', 'bigint') AS PhysicalReads,
event_data.value('(event/data[@name="logical_reads"]/value)[1]', 'bigint') AS LogicalReads,
event_data.value('(event/data[@name="writes"]/value)[1]', 'bigint') AS Writes,
event_data.value('(event/data[@name="row_count"]/value)[1]', 'bigint') AS RowCount,
event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS SQLText,
event_data.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS DatabaseName,
event_data.value('(event/action[@name="client_hostname"]/value)[1]', 'nvarchar(128)') AS ClientHost
FROM
(
SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file('C:\SQLLogs\SlowQueries*.xel', NULL, NULL, NULL)
) AS Events
ORDER BY EventTime DESC;
Aggregated Analysis
-- Top 10 slowest queries by total duration
WITH EventData AS (
SELECT
event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS duration,
event_data.value('(event/data[@name="cpu_time"]/value)[1]', 'bigint') AS cpu_time,
event_data.value('(event/data[@name="logical_reads"]/value)[1]', 'bigint') AS logical_reads,
event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS sql_text,
event_data.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS database_name
FROM
(
SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file('C:\SQLLogs\SlowQueries*.xel', NULL, NULL, NULL)
) AS Events
)
SELECT TOP 10
COUNT(*) AS ExecutionCount,
SUM(duration) / 1000 AS TotalDuration_ms,
AVG(duration) / 1000 AS AvgDuration_ms,
MAX(duration) / 1000 AS MaxDuration_ms,
SUM(logical_reads) AS TotalLogicalReads,
database_name,
LEFT(sql_text, 500) AS SQLPreview
FROM EventData
GROUP BY database_name, LEFT(sql_text, 500)
ORDER BY TotalDuration_ms DESC;
🔧 Advanced Session Configuration
Event Retention Modes
-- No event loss (may cause blocking if buffer full)
WITH (EVENT_RETENTION_MODE = NO_EVENT_LOSS)
-- Allow single event loss (recommended for most scenarios)
WITH (EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS)
-- Allow multiple event loss (lowest overhead)
WITH (EVENT_RETENTION_MODE = ALLOW_MULTIPLE_EVENT_LOSS)
Memory Configuration
-- Configure memory based on event volume
WITH (
MAX_MEMORY = 8192 KB, -- Buffer size
MAX_EVENT_SIZE = 0 KB, -- Unlimited event size
MEMORY_PARTITION_MODE = PER_CPU, -- Memory per CPU
TRACK_CAUSALITY = ON, -- Event correlation
MAX_DISPATCH_LATENCY = 30 SECONDS -- Write frequency
)
Predicate Optimization
-- Efficient predicate (evaluated first)
WHERE sqlserver.database_id = 5
-- Less efficient (requires string comparison)
WHERE sqlserver.database_name = N'AdventureWorks'
-- Combine predicates efficiently
WHERE
duration >= 1000000 -- Check cheap predicates first
AND sqlserver.database_id = 5
AND sqlserver.session_id <> sqlserver.is_system
✅ Production Best Practices
Session Template for Production
-- Production-ready monitoring template
CREATE EVENT SESSION [ProductionMonitoring] ON SERVER
ADD EVENT sqlserver.sql_statement_completed
(
ACTION
(
sqlserver.client_app_name,
sqlserver.client_hostname,
sqlserver.database_name,
sqlserver.plan_handle,
sqlserver.query_hash,
sqlserver.session_id,
sqlserver.sql_text,
sqlserver.username
)
WHERE
(
duration >= 1000000 -- 1 second
AND sqlserver.is_system = 0 -- Exclude system sessions
AND sqlserver.database_id > 4 -- Exclude system databases
)
),
ADD EVENT sqlserver.error_reported
(
ACTION
(
sqlserver.client_app_name,
sqlserver.database_name,
sqlserver.session_id,
sqlserver.sql_text
)
WHERE
(
severity >= 16 -- Significant errors only
)
)
ADD TARGET package0.event_file
(
SET filename = N'C:\SQLLogs\Production.xel',
max_file_size = 100,
max_rollover_files = 10
)
WITH
(
MAX_MEMORY = 4096 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 30 SECONDS,
MAX_EVENT_SIZE = 0 KB,
MEMORY_PARTITION_MODE = PER_CPU,
TRACK_CAUSALITY = OFF, -- Reduces overhead
STARTUP_STATE = ON
);
Monitoring Session Health
-- Check session status and statistics
SELECT
s.name AS SessionName,
s.create_time,
s.total_regular_buffers,
s.regular_buffer_size,
s.total_large_buffers,
s.large_buffer_size,
s.dropped_event_count,
s.dropped_buffer_count,
s.blocked_event_fire_time,
CASE WHEN s.blocked_event_fire_time > 0
THEN 'Blocking occurred'
ELSE 'OK' END AS Status
FROM sys.dm_xe_sessions s
WHERE s.name NOT LIKE 'system%';
-- Target statistics
SELECT
s.name AS SessionName,
t.target_name,
t.execution_count,
t.execution_duration_ms,
CAST(t.target_data AS XML).value('(RingBufferTarget/@eventCount)[1]', 'int') AS EventCount
FROM sys.dm_xe_session_targets t
INNER JOIN sys.dm_xe_sessions s ON t.event_session_address = s.address;
⚠️ Warning: If
dropped_event_countis increasing, consider increasingMAX_MEMORYor changingEVENT_RETENTION_MODE.
📈 Automated Reporting
Daily Slow Query Report
-- Create stored procedure for daily report
CREATE PROCEDURE dbo.GenerateSlowQueryReport
@StartDate DATETIME2 = NULL,
@EndDate DATETIME2 = NULL
AS
BEGIN
SET NOCOUNT ON;
IF @StartDate IS NULL
SET @StartDate = DATEADD(DAY, -1, CAST(GETDATE() AS DATE));
IF @EndDate IS NULL
SET @EndDate = CAST(GETDATE() AS DATE);
;WITH EventData AS (
SELECT
event_data.value('(event/@timestamp)[1]', 'datetime2') AS EventTime,
event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS duration,
event_data.value('(event/data[@name="cpu_time"]/value)[1]', 'bigint') AS cpu_time,
event_data.value('(event/data[@name="logical_reads"]/value)[1]', 'bigint') AS logical_reads,
event_data.value('(event/action[@name="query_hash"]/value)[1]', 'varbinary(8)') AS query_hash,
event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS sql_text,
event_data.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS database_name
FROM (
SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file('C:\SQLLogs\SlowQueries*.xel', NULL, NULL, NULL)
) AS Events
)
SELECT
database_name AS DatabaseName,
query_hash AS QueryHash,
COUNT(*) AS ExecutionCount,
SUM(duration) / 1000000 AS TotalDuration_sec,
AVG(duration) / 1000 AS AvgDuration_ms,
MAX(duration) / 1000 AS MaxDuration_ms,
SUM(logical_reads) AS TotalLogicalReads,
AVG(logical_reads) AS AvgLogicalReads,
LEFT(MAX(sql_text), 1000) AS SQLPreview
FROM EventData
WHERE EventTime >= @StartDate
AND EventTime < @EndDate
GROUP BY database_name, query_hash
ORDER BY TotalDuration_sec DESC;
END;
GO
Conclusion
Extended Events is the essential monitoring framework for modern SQL Server administration. Key takeaways:
- Low overhead - 1-3% impact vs 10-30% for SQL Trace
- Flexible filtering - Predicate expressions minimize data collection
- Scalable storage - File and ring buffer targets for different needs
- Rich data - Actions provide complete context for each event
- Production ready - Designed for 24/7 monitoring
Master Extended Events to gain unprecedented visibility into your SQL Server performance.
About the Author
Jamaurice Holt is a Cloud Database Expert and AWS Solutions Architect with over 10 years of experience in database optimization, high availability, and cloud migrations. Specializing in SQL Server, SQL optimization, and enterprise database solutions.
Related Articles
Need help with SQL Server optimization? Contact us for expert database consulting.