| name | sqlserver-diagnostics |
| description | Identify SQL Server performance bottlenecks using DMV queries. Covers wait stats, slow queries, active requests, performance counters, and connection analysis. |
SQL Server Diagnostics
Use this skill when a SQL Server instance is slow, queries are timing out, or you need to identify the source of a performance problem. Start here before diving into indexes or execution plans.
Recommended Workflow
1. Check wait stats โ scripts/wait-stats.sql
2. Find top slow queries โ scripts/top-slow-queries.sql
3. See what's running NOW โ scripts/active-queries.sql
4. Drill into a specific query โ sqlserver-execution-plans/SKILL.md
5. Act on findings โ sqlserver-indexes or sqlserver-query-optimization
1. Wait Stats Analysis
Wait stats are the single best indicator of what SQL Server is struggling with. Every time SQL Server can't proceed, it records a wait type. High cumulative waits tell you the category of problem.
Script: ../scripts/wait-stats.sql
SELECT TOP 20
wait_type,
wait_time_ms / 1000.0 AS wait_time_s,
(wait_time_ms - signal_wait_time_ms) / 1000.0 AS resource_wait_s,
signal_wait_time_ms / 1000.0 AS signal_wait_s,
waiting_tasks_count,
CAST(100.0 * wait_time_ms /
SUM(wait_time_ms) OVER () AS DECIMAL(5,2)) AS pct_of_total
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
'SLEEP_TASK','BROKER_TO_FLUSH','BROKER_TASK_STOP','CLR_AUTO_EVENT',
'CLR_MANUAL_EVENT','DISPATCHER_QUEUE_SEMAPHORE','FT_IFTS_SCHEDULER_IDLE_WAIT',
'HADR_FILESTREAM_IOMGR_IOCOMPLETION','HADR_WORK_QUEUE','LAZYWRITER_SLEEP',
'LOGMGR_QUEUE','ONDEMAND_TASK_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH',
'RESOURCE_QUEUE','SERVER_IDLE_CHECK','SLEEP_DBSTARTUP','SLEEP_DCOMSTARTUP',
'SLEEP_MASTERDBREADY','SLEEP_MASTERMDREADY','SLEEP_MASTERUPGRADED',
'SLEEP_MSDBSTARTUP','SLEEP_SYSTEMTASK','SLEEP_TEMPDBSTARTUP',
'SNI_HTTP_ACCEPT','SP_SERVER_DIAGNOSTICS_SLEEP','SQLTRACE_BUFFER_FLUSH',
'WAITFOR','XE_DISPATCHER_WAIT','XE_TIMER_EVENT','DBMIRROR_EVENTS_QUEUE',
'SQLTRACE_INCREMENTAL_FLUSH_SLEEP','WAIT_XTP_OFFLINE_CKPT_NEW_LOG',
'BROKER_EVENTHANDLER','CHECKPOINT_QUEUE','DBMIRROR_WORKER_QUEUE',
'SQLTRACE_WAIT_ENTRIES','WAIT_XTP_ONLINE_CKPT_NEW_LOG','ASYNC_NETWORK_IO'
)
ORDER BY wait_time_ms DESC;
Wait Type Interpretation Guide
| Wait Type | Meaning | Typical Fix |
|---|
PAGEIOLATCH_SH / PAGEIOLATCH_EX | Reading/writing data pages from disk (I/O bound) | Add RAM, faster storage, better indexes to reduce I/O |
LCK_M_* | Lock waits (blocking) | Find and fix blocking queries; review transaction isolation |
CXPACKET / CXCONSUMER | Parallelism โ threads waiting on each other | Review MAXDOP setting; fix skewed parallel queries |
SOS_SCHEDULER_YIELD | CPU pressure โ threads giving up CPU time | Query tuning, reduce CPU-intensive operations |
WRITELOG | Log writes โ transaction log is a bottleneck | Move log file to faster disk; reduce transaction size |
RESOURCE_SEMAPHORE | Memory grants waiting โ queries need more memory | Fix memory-spilling queries; increase max server memory |
ASYNC_NETWORK_IO | Results sent to client faster than client reads | Reduce result set sizes; check client application |
LATCH_EX / LATCH_SH | Internal memory structure contention | Usually TempDB contention โ add TempDB files |
PAGELATCH_* | In-memory page contention (TempDB allocation) | Add TempDB data files (8 files is common recommendation) |
HADR_* | Always On Availability Groups activity | Normal in AG environments; high values = AG lag |
Rule of thumb: If one wait type dominates (>30% of total), that's your problem category.
2. Top Slow Queries
Identify the queries consuming the most elapsed time, CPU, or I/O since SQL Server last restarted.
Script: ../scripts/top-slow-queries.sql
SELECT TOP 25
qs.total_elapsed_time / qs.execution_count / 1000.0 AS avg_elapsed_ms,
qs.total_elapsed_time / 1000.0 AS total_elapsed_ms,
qs.execution_count,
qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
qs.total_worker_time / qs.execution_count / 1000.0 AS avg_cpu_ms,
qs.total_logical_reads AS total_logical_reads,
DB_NAME(qt.dbid) AS database_name,
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS query_text,
qs.query_hash
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY avg_elapsed_ms DESC;
Key columns:
avg_elapsed_ms โ wall-clock time per execution (most useful for user-facing queries)
avg_logical_reads โ data pages read from buffer pool (index problems show up here)
avg_cpu_ms โ CPU time per execution (CPU-bound queries)
execution_count โ high count + high avg = highest total impact
3. Currently Running Queries
See what's actively executing right now, including wait info and blocking.
Script: ../scripts/active-queries.sql
SELECT
r.session_id,
s.login_name,
r.status,
r.wait_type,
r.wait_time / 1000.0 AS wait_time_s,
r.cpu_time / 1000.0 AS cpu_time_s,
r.logical_reads,
r.total_elapsed_time / 1000.0 AS elapsed_time_s,
r.blocking_session_id,
SUBSTRING(t.text, (r.statement_start_offset/2)+1,
((CASE r.statement_end_offset
WHEN -1 THEN DATALENGTH(t.text)
ELSE r.statement_end_offset
END - r.statement_start_offset)/2)+1) AS current_statement,
s.host_name,
s.program_name
FROM sys.dm_exec_requests r
JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id != @@SPID
AND s.is_user_process = 1
ORDER BY r.total_elapsed_time DESC;
What to look for:
blocking_session_id > 0 โ that session is being blocked (see monitoring/SKILL.md)
wait_type = 'PAGEIOLATCH_*' โ query doing heavy I/O right now
status = 'suspended' + long wait_time_s โ stuck on a resource
4. Performance Counters
Key performance counters via DMV (no need for PerfMon):
SELECT
counter_name,
instance_name,
cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name IN (
'Page life expectancy',
'Batch Requests/sec',
'SQL Compilations/sec',
'SQL Re-Compilations/sec',
'Page reads/sec',
'Page writes/sec',
'Lazy writes/sec',
'Memory Grants Pending',
'User Connections'
)
AND instance_name IN ('', 'SQLServer:Buffer Manager')
ORDER BY counter_name;
Key thresholds:
- Page Life Expectancy (PLE) < 300s: buffer pool is too small โ data is cycling out before reuse
- Memory Grants Pending > 0: queries waiting for memory grants โ potential sort/hash spills
- Lazy Writes/sec > 20: SQL Server is flushing dirty pages too aggressively โ add RAM
- SQL Re-Compilations/sec high: check for
WITH RECOMPILE stored procs or temp table schema changes
5. Connection Counts
SELECT
DB_NAME(dbid) AS database_name,
loginame,
COUNT(*) AS connection_count
FROM sys.sysprocesses
WHERE dbid > 0
GROUP BY dbid, loginame
ORDER BY connection_count DESC;
SELECT COUNT(*) AS total_connections
FROM sys.dm_exec_connections;
6. Memory Usage
SELECT
DB_NAME(database_id) AS database_name,
COUNT(*) * 8 / 1024.0 AS buffer_pool_mb
FROM sys.dm_os_buffer_descriptors
WHERE database_id > 4
GROUP BY database_id
ORDER BY buffer_pool_mb DESC;
SELECT
physical_memory_in_use_kb / 1024 AS memory_used_mb,
page_fault_count
FROM sys.dm_os_process_memory;
Diagnostic Checklist
When a performance complaint comes in: