In this lesson, you will learn the tools and techniques DBAs use to monitor SQL Server performance in the real world.
Monitoring is not “looking at CPU.” Monitoring is building a system that can answer questions fast:
What is slow? Why is it slow? When did it start? What changed?
Is it getting worse?
Big Idea: The goal of monitoring is fast diagnosis with evidence.
When production is slow, the DBA who wins is the DBA who can point to specific metrics, specific queries, and specific bottlenecks—
not guesses.
120–180 minutes (concepts + tools + labs)
There are two monitoring modes:
Key teaching point: If you only monitor during incidents, you will miss the “slow creep.”
If you only do long-term charts, you may fail during a live outage. You need both.
Monitoring is about measuring the right things. These are the most important categories:
Key teaching point: A slow system is usually caused by one limiting resource (CPU, I/O, blocking, memory grants),
not by “SQL Server being slow.”
DMVs are your “live dashboard.” They show what SQL Server is doing right now (and some aggregated history).
DMVs are the first stop during an incident.
Query Store captures query performance over time. It helps you answer:
“Did this query become slower after a deployment?” and “Which plan change caused regression?”
Extended Events is the modern replacement for Profiler/Trace for detailed event capture
(deadlocks, long queries, wait events, errors). It is powerful and usually lower overhead when configured correctly.
PerfMon provides OS-level counters for CPU, memory, disk, and SQL Server objects. It is essential for understanding
whether the bottleneck is SQL Server internal or the OS/hardware layer.
Key teaching point: You do not need 50 tools. You need the right tool for the question you are answering.
Wait stats show where SQL Server spends time waiting. They are one of the fastest ways to narrow down bottlenecks—
but only if interpreted in context.
Key teaching point: Wait stats tell you “where time went,” but you still must identify
the specific queries and patterns causing the waits.
The following scripts are a practical “first response kit.” They are safe and read-only.
Students should practice running these scripts and interpreting results.
/* Active requests (what's running now?) */
SELECT
r.session_id,
r.status,
r.command,
r.cpu_time,
r.total_elapsed_time,
r.logical_reads,
r.reads,
r.writes,
r.wait_type,
r.wait_time,
r.wait_resource,
DB_NAME(r.database_id) AS database_name,
SUBSTRING(t.text, (r.statement_start_offset/2)+1,
CASE WHEN r.statement_end_offset = -1 THEN LEN(CONVERT(nvarchar(max), t.text))
ELSE (r.statement_end_offset - r.statement_start_offset)/2 + 1
END) AS running_statement
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id <> @@SPID
ORDER BY r.total_elapsed_time DESC;
How to interpret: Look for long elapsed time, high logical reads, high CPU, and blocking waits.
The running statement tells you what query is actually executing.
/* Blocking overview */
SELECT
r.session_id,
r.blocking_session_id,
r.status,
r.wait_type,
r.wait_time,
r.wait_resource,
r.total_elapsed_time,
SUBSTRING(t.text, (r.statement_start_offset/2)+1,
CASE WHEN r.statement_end_offset = -1 THEN LEN(CONVERT(nvarchar(max), t.text))
ELSE (r.statement_end_offset - r.statement_start_offset)/2 + 1
END) AS statement_text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0
ORDER BY r.wait_time DESC;
How to interpret: Blocking often causes a “slow system” even if CPU is low.
The next step is to inspect the blocking session and transaction duration.
/* Top waits (since last restart) - starting point */ SELECT TOP (20) wait_type, wait_time_ms, signal_wait_time_ms, waiting_tasks_count FROM sys.dm_os_wait_stats WHERE wait_type NOT LIKE 'SLEEP%' ORDER BY wait_time_ms DESC;
How to interpret: Treat as a clue. For real incident analysis, focus on waits during the incident window,
not only since restart.
/* Top cached queries by total logical reads */ SELECT TOP (20) (qs.total_logical_reads) AS total_logical_reads, (qs.total_logical_reads / NULLIF(qs.execution_count,0)) AS avg_logical_reads, qs.execution_count, (qs.total_worker_time / NULLIF(qs.execution_count,0)) AS avg_cpu, (qs.total_elapsed_time / NULLIF(qs.execution_count,0)) AS avg_elapsed, DB_NAME(st.dbid) AS database_name, LEFT(st.text, 4000) AS query_text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.total_logical_reads DESC;
How to interpret: High logical reads indicates inefficient query patterns (scans, poor indexes, large row processing).
These queries are top candidates for tuning.
Query Store helps you see performance changes over time. It is essential in modern SQL Server tuning because it provides:
query history, plan history, runtime stats, and the ability to detect when a query got worse.
Key teaching point: If you tune without Query Store (or equivalent history), you will repeat problems.
History is what gives you control.
Alerts should be actionable. If every alert is “CPU is high,” you will ignore the system.
A good monitoring strategy uses thresholds and context.
Key teaching point: The best alert is the one that tells you “what to check next.”
Students will build a simple monitoring snapshot script pack that captures enough information to diagnose a typical slowdown.
Prompt:
“If you could only collect 5 metrics from SQL Server for long-term monitoring, what would they be and why?
How would those 5 metrics help you diagnose most incidents?”
In the next lesson, we will cover SQL Server Storage and I/O Performance.
You will learn how SQL Server reads/writes data, how to measure disk latency correctly,
the difference between data file I/O and log I/O, and how to diagnose storage bottlenecks using evidence.
Not a member yet? Register now
Are you a member? Login now