Home / SQL / SQL Server Monitoring: DMVs, Query Store, and Performance Baselines
SQL

SQL Server Monitoring: DMVs, Query Store, and Performance Baselines

Monitor SQL Server with Dynamic Management Views (DMVs), Query Store for query performance tracking, Extended Events for lightweight tracing, Performance Mon...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

event_data.value('(event/@name)[1]', 'VARCHAR(50)') AS event_name, event_data.value('(event/@timestamp)[1]', 'DATETIME2') AS event_timestamp, 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_time_ms, event_data.value('(event/data[@name="logical_reads"]/value)[1]', 'BIGINT') AS logical_reads, event_data.value('(event/data[@name="physical_reads"]/value)[1]', 'BIGINT') AS physical_reads, event_data.value('(event/data[@name="writes"]/value)[1]', 'BIGINT') AS writes, 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, event_data.value('(event/action[@name="username"]/value)[1]', 'NVARCHAR(128)') AS username, event_data.value('(event/action[@name="client_hostname"]/value)[1]', 'NVARCHAR(128)') AS client_hostname``` FROM (

SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file('C:\ExtendedEvents\SlowQueries*.xel', NULL, NULL, NULL)```
) AS XEventData
ORDER BY event_timestamp DESC;

Deadlock Capture

-- Create deadlock capture session
CREATE EVENT SESSION DeadlockCapture ON SERVER
ADD EVENT sqlserver.xml_deadlock_report
ADD TARGET package0.event_file (
```text
SET filename = 'C:\ExtendedEvents\Deadlocks.xel'```
);

ALTER EVENT SESSION DeadlockCapture ON SERVER STATE = START;

-- Read deadlock graphs
SELECT 
```text
event_data.value('(event/@timestamp)[1]', 'DATETIME2') AS event_timestamp,
CAST(event_data.query('(event/data[@name="xml_report"]/value/deadlock)[1]') AS XML) AS deadlock_graph```
FROM (
```sql
SELECT CAST(event_data AS XML) AS event_data
FROM sys.fn_xe_file_target_read_file('C:\ExtendedEvents\Deadlocks*.xel', NULL, NULL, NULL)```
) AS XEventData
ORDER BY event_timestamp DESC;

Performance Baselines

Establishing Baseline

# PowerShell script to collect baseline metrics
$server = "localhost"
$database = "AdventureWorks"
$outputPath = "C:\Baselines"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"

## Collect baseline data
$queries = @{
```sql
"WaitStats" = "SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0"
"PerfCounters" = @"
    SELECT 
        object_name,
        counter_name,
        instance_name,
        cntr_value
    FROM sys.dm_os_performance_counters
    WHERE counter_name IN (
        'Page life expectancy',
        'Buffer cache hit ratio',
        'Batch requests/sec',





        'SQL Compilations/sec',
        'SQL Re-Compilations/sec'
    )```
"@
```sql
"IndexUsage" = "SELECT * FROM sys.dm_db_index_usage_stats WHERE database_id = DB_ID()"
"QueryStats" = "SELECT TOP 100 * FROM sys.dm_exec_query_stats ORDER BY total_worker_time DESC"```
}

foreach ($key in $queries.Keys) {
```text
$query = $queries[$key]
$outputFile = "$outputPath\Baseline_${key}_${timestamp}.csv"

Invoke-Sqlcmd -ServerInstance $server -Database $database -Query $query |
    Export-Csv -Path $outputFile -NoTypeInformation

Write-Host "Exported $key baseline to $outputFile"```
}

Proactive Monitoring Script

Proactive Monitoring Script

Figure: Activity Monitor – resource usage graphs and expensive queries.





## Daily health check script
$server = "localhost"
$emailTo = "dba@contoso.com"
$emailFrom = "sqlalerts@contoso.com"
$smtpServer = "smtp.contoso.com"





$healthChecks = @()

## Check 1: Long-running queries
$longRunningQueries = Invoke-Sqlcmd -ServerInstance $server -Query @"
```sql
SELECT COUNT(*) AS QueryCount
FROM sys.dm_exec_requests
WHERE total_elapsed_time > 300000  -- 5 minutes
    AND session_id <> @@SPID```
"@





if ($longRunningQueries.QueryCount -gt 0) {

> **Architecture Overview:** $healthChecks = "⚠️ $($longRunningQueries.QueryCount) long running queries detected"```

SELECT COUNT(DISTINCT blocking_session_id) AS BlockingCount
FROM sys.dm_os_waiting_tasks
WHERE blocking_session_id <> 0```
"@





if ($blocking.BlockingCount -gt 0) {

> **Architecture Overview:** $healthChecks = "⚠️ $($blocking.BlockingCount) blocking sessions detected"```

SELECT COUNT(*) AS FailedJobCount
FROM msdb.dbo.sysjobhistory
WHERE run_status = 0  -- Failed
    AND run_date = CONVERT(INT, CONVERT(VARCHAR(8), GETDATE(), 112))```
"@





if ($failedJobs.FailedJobCount -gt 0) {
```text
$healthChecks += "❌ $($failedJobs.FailedJobCount) failed jobs today"```
}

## Send alert if issues found
if ($healthChecks.Count -gt 0) {
```text
$body = "SQL Server Health Check Alert`n`n" + ($healthChecks -join "`n")
Send-MailMessage -To $emailTo -From $emailFrom -Subject "SQL Server Health Alert" -Body $body -SmtpServer $smtpServer```
}





Architecture Decision and Tradeoffs

When designing data management solutions with SQL Server, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/sql/
  • https://learn.microsoft.com/azure/azure-sql/
  • https://learn.microsoft.com/fabric/database/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/sql/
  • Sample repositories: https://github.com/microsoft/sql-server-samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Use DMVs for real-time query and performance monitoring
  • Enable Query Store for query performance history
  • Implement Extended Events for lightweight tracing
  • Establish performance baselines during normal operation
  • Monitor wait statistics to identify bottlenecks
  • Track index usage to optimize indexing strategy
  • Set up proactive monitoring with PowerShell automation
  • Configure SQL Server Agent alerts for critical conditions
  • Review deadlocks and blocking regularly
  • Analyze I/O latency for storage performance

Next Steps

  • Enable Query Store on production databases
  • Create Extended Events sessions for slow queries
  • Establish baseline metrics for comparison
  • Automate daily health check reports
  • Configure SQL Server Agent alerts

Additional Resources


Monitor. Analyze. Optimize. Prevent.

Discussion