Top 10 SQL Server Monitoring Tools in 2026
Chat2DB TeamMonitoring a SQL Server instance splits cleanly into two jobs, and most "best tools" lists only discuss the first one.
The first job is detection: something is collecting wait statistics, plan history, blocking chains and file I/O latency around the clock, and telling you when a number moves. The second job is remediation: someone opens the offending query, reads its actual execution plan, rewrites it, tests the rewrite against real data, and ships an index change. A monitoring agent is excellent at the first and does essentially nothing for the second.
This list is ranked by how much of your total time-to-resolution each tool removes, which is why a SQL workbench sits at the top rather than an agent. Where a tool is commercial, tiers are described qualitatively — vendors reprice and repackage constantly, and quoting a number here would only mislead you. Check each vendor's own pricing page before you budget.
How to evaluate a SQL monitor tool
Six capabilities separate genuine database monitoring from a CPU graph with a SQL Server logo on it:
- Query-level attribution. Not "the server is at 95% CPU" but "these four statements are 70% of the worker time". Total cost matters more than worst single execution — a 4 ms query executed 300,000 times an hour beats a 30-second nightly report.
- Plan history and regression detection. Most overnight performance collapses are plan flips caused by shifting statistics or parameter sniffing. Without stored plan history you are guessing.
- Wait statistics over time. Wait types tell you whether you have an indexing problem, an I/O problem, a memory-grant problem or a locking problem. Point-in-time waits are much less useful than a trend.
- Blocking and deadlock capture. Deadlocks are transient by definition; if nothing captured the graph when it happened, the incident is unsolvable after the fact.
- Coverage across your estate. On-prem instances, Azure SQL Database, Azure SQL Managed Instance, Amazon RDS for SQL Server and containers rarely all report through the same agent.
- Low collection overhead. A monitor that materially loads the instance it watches is a liability during exactly the incidents you bought it for.
Keep those six in mind and the list below sorts itself.
The ranked list
1. Chat2DB
Chat2DB (opens in a new tab) is an AI-assisted database client and SQL workbench, not an APM product — and that is the point. It is ranked first because it owns the half of the workflow every monitoring tool hands back to you unfinished.
A monitor raises an alert that says a stored procedure's average duration tripled. What you do next is connect to the instance, pull the query text, look at the execution plan, check index usage on the tables it touches, write a candidate rewrite, run both versions, and compare. That entire loop happens in a SQL client. Chat2DB is built for that loop: multi-engine connections (SQL Server alongside MySQL, PostgreSQL, Oracle, ClickHouse and others), schema browsing, saved queries you can pin and re-run, result comparison, and natural-language-to-SQL for when you need a diagnostic query you have not memorised — which, given the shape of the DMV catalog, is most of them.
Be clear about what it is not. It does not run a background agent, does not page you at 3am, does not store a year of wait-stat history and does not replace Redgate SQL Monitor or Datadog. Pair it with one of those. The realistic stack for most teams is one always-on collector plus one good client, and the client is where the hours actually go.
There is a desktop build at chat2db.ai/download (opens in a new tab) and a browser version at app.chat2db.ai (opens in a new tab) if you would rather not install anything.
Best for: investigating and fixing what your monitor flags, across a mixed-engine estate.
2. Redgate SQL Monitor
The specialist. SQL Monitor is built for SQL Server first and shows it — the metric set, the alert definitions and the analysis views all assume SQL Server semantics rather than generic database concepts. Agentless collection, an estate overview that scales to hundreds of instances, deep query and wait analysis, deadlock graph capture, and an alert catalogue that ships already tuned to things SQL Server DBAs actually care about (log growth, job failures, availability group health, backup overdue).
Licensing is per monitored instance with a trial available. If SQL Server is your primary platform and you have a DBA function, this is the default recommendation.
Best for: SQL Server-centric shops that want the deepest platform-specific coverage.
3. SolarWinds Database Performance Analyzer (DPA)
DPA's distinguishing idea is response-time analysis: everything is expressed as where time was spent, broken down by wait type, per query, per hour, over months of retained history. That framing is unusually good at answering "when did this start and what changed", because you can put two weeks side by side and see the wait profile shift.
It is also multi-platform — SQL Server, Oracle, MySQL, PostgreSQL, Db2, Sybase — with a single agentless collector, so it suits heterogeneous estates. Licensing is per monitored instance.
Best for: historical trend analysis and mixed-vendor environments.
4. SolarWinds SQL Sentry (formerly SentryOne)
SQL Sentry came from SentryOne and retains that product's strengths: a genuinely excellent performance dashboard, best-in-class SQL Agent job calendar and scheduling visualisation, Top SQL with plan capture, deadlock analysis, and Plan Explorer integration for plan reading. If your pain is "our overnight batch window collides with itself and we cannot see why", the event calendar view is worth the evaluation on its own.
Best for: Agent job and batch-window scheduling problems, plus strong general performance monitoring.
5. Datadog Database Monitoring
If your organisation already runs Datadog, the database monitoring product is the path of least resistance. It correlates query-level SQL Server metrics — normalised query performance, execution plans, blocking, host metrics — with the application traces, logs and infrastructure metrics already in the platform. Being able to pivot from a slow API endpoint's trace straight into the statement it executed is a real workflow advantage that standalone database tools cannot match.
The trade-off is depth and cost model: it is priced per database host on top of your existing Datadog spend, and the SQL Server-specific analysis is not as deep as a specialist product. Worth it when the correlation matters more than the depth.
Best for: teams already standardised on Datadog who want application-to-query correlation.
6. Quest Spotlight on SQL Server
Spotlight's signature is its live diagnostic dashboard — a flow diagram of the instance showing sessions, memory, disk and network with animated data movement between components. It is genuinely useful for triage, because a saturated component is visible at a glance rather than inferable from a dozen charts. Quest pairs it with alarms, a playback feature for reviewing past periods, and integration with the rest of the Quest SQL Server line.
Best for: real-time triage and teams who want visual diagnosis rather than dashboards of counters.
7. dbWatch Control Center
dbWatch takes an estate-management angle rather than a deep-diagnostics one: it is designed for one DBA responsible for a large, heterogeneous fleet. Health checks, maintenance tasks, capacity/growth forecasting and compliance reporting across SQL Server, Oracle, PostgreSQL, MySQL and others, with both on-prem and cloud instances in the same inventory. Less about tuning one query, more about knowing that 340 instances are all backed up, all within disk headroom, and all patched.
Best for: managing many instances across multiple database platforms.
8. Zabbix, Prometheus and the open-source route
The "sql server monitoring tools open source" answer is a stack rather than a product, and it is a legitimate one:
- Zabbix ships an official SQL Server template using ODBC that collects performance counters, database states, backup ages and replication health, with alerting and dashboards included.
- Prometheus plus
sql_exporter(or the communitymssql_exporter) lets you define arbitrary T-SQL queries as metric collectors and graph the results in Grafana. Because you write the queries, you can expose exactly the DMV data you care about. - SQLWatch is a purpose-built, SQL Server-native open-source monitoring solution: it installs into your instances, collects performance counters, waits, index and file statistics into its own repository, and ships Grafana and Power BI dashboards.
- dbatools, the PowerShell module, is not a monitor but is the automation backbone most open-source setups end up using for collection and health checks.
The honest cost accounting: licence fees of zero, engineering time distinctly non-zero. You are the vendor. This route makes sense when you have the in-house skills and an instance count large enough that per-instance licensing hurts.
Best for: teams with platform-engineering capacity and a large instance count.
9. Query Store and Extended Events (built in, free)
Every supported SQL Server edition includes two first-class monitoring features that many teams still leave switched off.
Query Store is a flight recorder for query performance: it persists query text, every plan the optimizer has chosen, and runtime statistics per time interval, inside the user database. It survives restarts, it is queryable with plain T-SQL, and it can force a known-good plan when the optimizer picks a bad one. For diagnosing regressions it is the single highest-value free feature in the product.
Extended Events replaced SQL Trace and Profiler as the low-overhead event capture engine. Capture long-running batches, deadlock graphs, page splits, errors, or almost any other instrumented event, filtered server-side so the overhead stays small.
Neither gives you alerting or a fleet view. Both give you the raw truth, on every instance you own, at no cost.
10. Performance Monitor and the DMVs (built in, free)
Performance Monitor (PerfMon) plus the SQL Server counter set covers the OS-level view: page life expectancy, batch requests per second, compilations, buffer cache hit ratio, disk queue lengths. Data collector sets let you schedule captures to a file for later analysis.
Dynamic management views are where the instance tells you what it is doing right now and what it has done since it started. They cost nothing, they are always there, and they answer most questions if you know which ones to ask. The next section gives you the ones worth saving.
Free T-SQL you can run right now
Every query below runs against a stock SQL Server instance with no extra installation. One caveat applies to all the sys.dm_exec_* and sys.dm_os_wait_stats views: their counters accumulate since service start and reset on restart. Always check your baseline first.
SELECT
sqlserver_start_time,
DATEDIFF(hour, sqlserver_start_time, GETDATE()) AS hours_of_data
FROM sys.dm_os_sys_info;The queries burning the most CPU
SELECT TOP 20
qs.total_worker_time / 1000 AS total_cpu_ms,
qs.execution_count,
(qs.total_worker_time / qs.execution_count) / 1000 AS avg_cpu_ms,
qs.total_elapsed_time / 1000 AS total_elapsed_ms,
qs.total_logical_reads,
qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
DB_NAME(st.dbid) AS database_name,
SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset) / 2) + 1) AS statement_text,
qs.last_execution_time
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
ORDER BY qs.total_worker_time DESC;Sort by total_worker_time rather than average duration. The statement at the top of this list is almost never the one users complain about — it is usually a cheap query called an enormous number of times, and fixing it frees more CPU than tuning the slow report ever will. Swap the ORDER BY to qs.total_logical_reads DESC to find the I/O hogs instead.
What is running and what is blocking
SELECT
r.session_id,
r.blocking_session_id,
r.status,
r.command,
r.wait_type,
r.wait_time AS wait_time_ms,
r.wait_resource,
r.cpu_time,
r.total_elapsed_time,
DB_NAME(r.database_id) AS database_name,
s.login_name,
s.host_name,
s.program_name,
t.text AS batch_text
FROM sys.dm_exec_requests AS r
JOIN sys.dm_exec_sessions AS s
ON s.session_id = r.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.session_id <> @@SPID
AND s.is_user_process = 1
ORDER BY r.blocking_session_id DESC, r.wait_time DESC;Read the blocking_session_id column first. Any non-zero value means that session is waiting on another; walk the chain up until you find the session blocking others while blocked by nobody. That is the head of the blocking chain and the only one worth acting on.
Where the instance spends its time waiting
SELECT TOP 15
wait_type,
wait_time_ms / 1000.0 AS total_wait_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 waiting_tasks_count > 0
AND wait_type NOT IN (
'CLR_SEMAPHORE','LAZYWRITER_SLEEP','RESOURCE_QUEUE','SLEEP_TASK',
'SLEEP_SYSTEMTASK','SQLTRACE_BUFFER_FLUSH','WAITFOR','LOGMGR_QUEUE',
'CHECKPOINT_QUEUE','REQUEST_FOR_DEADLOCK_SEARCH','XE_TIMER_EVENT',
'BROKER_TO_FLUSH','BROKER_TASK_STOP','CLR_MANUAL_EVENT','CLR_AUTO_EVENT',
'DISPATCHER_QUEUE_SEMAPHORE','FT_IFTS_SCHEDULER_IDLE_WAIT',
'XE_DISPATCHER_WAIT','XE_DISPATCHER_JOIN','BROKER_EVENTHANDLER',
'SQLTRACE_INCREMENTAL_FLUSH_SLEEP','DIRTY_PAGE_POLL','QDS_ASYNC_QUEUE',
'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP','QDS_SHUTDOWN_QUEUE','HADR_WORK_QUEUE',
'HADR_TIMER_TASK','HADR_CLUSAPI_CALL','SP_SERVER_DIAGNOSTICS_SLEEP',
'SLEEP_BPOOL_FLUSH','SLEEP_DBSTARTUP','SLEEP_DCOMSTARTUP'
)
ORDER BY wait_time_ms DESC;The exclusion list filters out benign idle waits that would otherwise swamp the results. A rough reading guide for what remains: PAGEIOLATCH_* points at storage or at missing indexes forcing scans; LCK_M_* is blocking; CXPACKET and CXCONSUMER relate to parallelism settings; RESOURCE_SEMAPHORE means queries are queuing for memory grants; WRITELOG points at transaction-log storage latency. High signal_wait_s relative to total is a CPU-pressure signal — tasks are ready to run but waiting for a scheduler.
To reset the counters after a fix so you can measure the effect cleanly:
DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR);Storage latency per database file
SELECT
DB_NAME(vfs.database_id) AS database_name,
mf.name AS logical_name,
mf.type_desc,
mf.physical_name,
vfs.num_of_reads,
CASE WHEN vfs.num_of_reads = 0 THEN 0
ELSE vfs.io_stall_read_ms / vfs.num_of_reads END AS avg_read_stall_ms,
vfs.num_of_writes,
CASE WHEN vfs.num_of_writes = 0 THEN 0
ELSE vfs.io_stall_write_ms / vfs.num_of_writes END AS avg_write_stall_ms,
CAST(vfs.size_on_disk_bytes / 1024.0 / 1024 AS DECIMAL(12,1)) AS size_mb
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
JOIN sys.master_files AS mf
ON mf.database_id = vfs.database_id
AND mf.file_id = vfs.file_id
ORDER BY avg_read_stall_ms DESC;This is the fastest way to settle the "is it the storage?" argument. Compare the stall figures against what your storage vendor claims the volume should deliver, and pay particular attention to log files, which are latency-sensitive in a way data files are not.
Index usage and missing index suggestions
Indexes that cost you write throughput and return nothing:
SELECT
OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,
OBJECT_NAME(i.object_id) AS table_name,
i.name AS index_name,
ISNULL(us.user_seeks, 0) AS user_seeks,
ISNULL(us.user_scans, 0) AS user_scans,
ISNULL(us.user_lookups, 0) AS user_lookups,
ISNULL(us.user_updates, 0) AS user_updates
FROM sys.indexes AS i
LEFT JOIN sys.dm_db_index_usage_stats AS us
ON us.object_id = i.object_id
AND us.index_id = i.index_id
AND us.database_id = DB_ID()
WHERE i.type_desc = 'NONCLUSTERED'
AND OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1
ORDER BY ISNULL(us.user_seeks, 0) + ISNULL(us.user_scans, 0) + ISNULL(us.user_lookups, 0) ASC,
ISNULL(us.user_updates, 0) DESC;Indexes at the top with high user_updates and no reads are pure write overhead — but check your uptime first, because an index used only by a quarterly job will look unused for months.
And what the optimizer wishes it had:
SELECT TOP 20
ROUND(s.avg_total_user_cost * s.avg_user_impact * (s.user_seeks + s.user_scans), 0)
AS improvement_measure,
d.statement AS table_name,
d.equality_columns,
d.inequality_columns,
d.included_columns,
s.user_seeks,
s.user_scans,
s.last_user_seek
FROM sys.dm_db_missing_index_groups AS g
JOIN sys.dm_db_missing_index_group_stats AS s
ON s.group_handle = g.index_group_handle
JOIN sys.dm_db_missing_index_details AS d
ON d.index_handle = g.index_handle
ORDER BY improvement_measure DESC;Treat this output as a hint, never as a work order. The missing index DMVs over-suggest badly: they propose overlapping and near-duplicate indexes, they ignore indexes you already have, they never account for write cost, and avg_user_impact is an optimizer estimate rather than a measurement. Use it to find which tables are being scanned, then design the index yourself by reading the actual plans.
Query Store: finding the regression
Enable it per database if it is not already on:
ALTER DATABASE [YourDatabase] SET QUERY_STORE = ON
(OPERATION_MODE = READ_WRITE,
DATA_FLUSH_INTERVAL_SECONDS = 900,
INTERVAL_LENGTH_MINUTES = 15,
MAX_STORAGE_SIZE_MB = 2048,
QUERY_CAPTURE_MODE = AUTO,
SIZE_BASED_CLEANUP_MODE = AUTO);Then find the slowest statements over the last day:
SELECT TOP 20
q.query_id,
p.plan_id,
qt.query_sql_text,
rs.execution_count,
rs.avg_duration / 1000.0 AS avg_duration_ms,
rs.avg_cpu_time / 1000.0 AS avg_cpu_ms,
rs.avg_logical_io_reads,
rs.last_execution_time
FROM sys.query_store_query AS q
JOIN sys.query_store_query_text AS qt
ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan AS p
ON p.query_id = q.query_id
JOIN sys.query_store_runtime_stats AS rs
ON rs.plan_id = p.plan_id
JOIN sys.query_store_runtime_stats_interval AS rsi
ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
WHERE rsi.start_time > DATEADD(day, -1, GETUTCDATE())
ORDER BY rs.avg_duration DESC;The regression-hunting query — statements the optimizer has produced more than one plan for:
SELECT
q.query_id,
COUNT(DISTINCT p.plan_id) AS plan_count,
MIN(qt.query_sql_text) AS query_text
FROM sys.query_store_query AS q
JOIN sys.query_store_plan AS p
ON p.query_id = q.query_id
JOIN sys.query_store_query_text AS qt
ON qt.query_text_id = q.query_text_id
GROUP BY q.query_id
HAVING COUNT(DISTINCT p.plan_id) > 1
ORDER BY plan_count DESC;Compare the runtime statistics of each plan for a given query_id. When one plan is consistently better, you can pin it:
EXEC sp_query_store_force_plan @query_id = 42, @plan_id = 17;Forcing a plan is a tourniquet, not a cure — it stops the bleeding while you fix the statistics, parameterisation or indexing that caused the flip. Record every forced plan somewhere visible so it gets revisited.
Capturing slow queries with Extended Events
CREATE EVENT SESSION [slow_queries] ON SERVER
ADD EVENT sqlserver.sql_batch_completed (
ACTION (sqlserver.database_name, sqlserver.client_app_name,
sqlserver.username, sqlserver.session_id)
WHERE duration > 5000000
),
ADD EVENT sqlserver.rpc_completed (
ACTION (sqlserver.database_name, sqlserver.client_app_name,
sqlserver.username, sqlserver.session_id)
WHERE duration > 5000000
)
ADD TARGET package0.event_file (
SET filename = N'slow_queries.xel',
max_file_size = 100,
max_rollover_files = 5
)
WITH (MAX_MEMORY = 8192 KB,
EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 30 SECONDS,
STARTUP_STATE = ON);
GO
ALTER EVENT SESSION [slow_queries] ON SERVER STATE = START;duration is in microseconds, so the filter above captures anything over five seconds. Filtering server-side in the WHERE clause is what keeps the overhead negligible — an unfiltered session on a busy instance is a different proposition entirely. Read the results back with sys.fn_xe_file_target_read_file.
Key health counters in one query
SELECT
RTRIM(object_name) AS object_name,
RTRIM(counter_name) AS counter_name,
RTRIM(instance_name) AS instance_name,
cntr_value
FROM sys.dm_os_performance_counters
WHERE RTRIM(counter_name) IN (
'Page life expectancy',
'Batch Requests/sec',
'SQL Compilations/sec',
'SQL Re-Compilations/sec',
'Buffer cache hit ratio',
'Lock Waits/sec',
'Memory Grants Pending'
);Counter names in this view are space-padded, which is why the RTRIM calls are there — comparing without them is the classic reason this query returns nothing. Note also that rate-style counters such as "Batch Requests/sec" are cumulative in the DMV: sample twice and take the difference over elapsed time rather than reading cntr_value as an instantaneous rate.
Building a monitoring stack without a licence
If budget is zero, this combination covers most of what a commercial tool would give you:
- Turn on Query Store in every user database. This is the highest-value single action on the list and it takes one statement per database.
- Create a filtered Extended Events session for long-running batches and one for deadlock graphs, with
STARTUP_STATE = ONso they survive restarts. - Snapshot the DMVs on a schedule. A SQL Agent job that inserts the wait-stats, file-stats and query-stats queries above into a history table every 15 minutes gives you the trend data the DMVs themselves cannot provide, because they reset on restart.
- Graph it. Point Grafana at that history table, or deploy SQLWatch if you would rather not build the collection layer yourself.
- Alert on deltas, not absolutes. A page life expectancy of 300 means nothing without knowing yesterday's value.
- Keep a client open for the investigation half. Save the queries from this article as named snippets in whatever client you use — Chat2DB (opens in a new tab) handles this well across multiple engines if your estate is not SQL Server only — so that responding to an alert starts with running a diagnostic rather than searching for one.
The gap between this and a commercial product is not data quality; the DMVs are the same data every vendor reads. The gap is packaging: alert routing, retention management, estate-wide rollups, and someone else maintaining the collection when a new SQL Server version changes a view.
Choosing
- SQL Server is your primary platform and you have a DBA: Redgate SQL Monitor for detection, a workbench like Chat2DB for remediation.
- Mixed database vendors, deep history matters: SolarWinds DPA.
- Batch and Agent job scheduling is the pain: SQL Sentry.
- Already on Datadog: Datadog Database Monitoring, for the trace-to-query pivot.
- Large fleet, small team: dbWatch, or Zabbix with the official template.
- No budget: Query Store plus Extended Events plus scheduled DMV snapshots, graphed in Grafana.
Whichever collector you pick, the alert is only ever the beginning of the work. Budget as much attention to the tool you will use to read plans and rewrite queries as to the one that sends the notification — that is where the time actually goes.
