How to Identify and Eliminate Expensive Queries in SQL Server
Introduction
In any database environment, a small number of expensive queries often consume a disproportionate amount of resources. Identifying and optimizing these resource-intensive queries is one of the most impactful performance tuning activities a DBA or developer can perform. This guide walks you through practical techniques to find, analyze, and fix your most expensive SQL Server queries.
Section 1: Identifying Expensive Queries
1.1 Using SQL Server's Built-in Tools
Dynamic Management Views (DMVs):
-- Top 50 most CPU-intensive queries
SELECT TOP 50
qs.total_worker_time/qs.execution_count AS avg_cpu_time,
qs.total_elapsed_time/qs.execution_count AS avg_duration,
qs.execution_count,
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,
qt.text AS full_query_text,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_worker_time DESC;
Query Store (SQL Server 2016+):
-- Find top resource consumers in Query Store
SELECT TOP 10
rs.avg_cpu_time,
rs.avg_duration,
rs.avg_logical_io_reads,
rs.count_executions,
qt.query_sql_text
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
ORDER BY rs.avg_cpu_time DESC;
1.2 Using SQL Server Profiler and Extended Events
Extended Events Session for High-Cost Queries:
CREATE EVENT SESSION [HighCostQueries] ON SERVER
ADD EVENT sqlserver.sql_statement_completed
(
WHERE ([duration] > 1000000) -- 1 second
ACTION (
sqlserver.sql_text,
sqlserver.plan_handle,
sqlserver.client_hostname,
sqlserver.username
)
)
ADD TARGET package0.event_file(SET filename=N'HighCostQueries')
WITH (MAX_MEMORY=4096 KB, MAX_DISPATCH_LATENCY=30 SECONDS);
1.3 Using Performance Dashboard Reports
SQL Server Management Studio includes built-in performance reports that can quickly surface expensive queries:
-
Right-click your server in Object Explorer
-
Select Reports > Standard Reports
-
Choose Performance - Top Queries by Average CPU Time or other relevant options
Section 2: Analyzing Expensive Queries
2.1 Understanding Query Patterns
Common causes of expensive queries:
-
Large result sets
-
Missing/inadequate indexes
-
Complex joins
-
Excessive sorting/aggregation
-
Cursor-based operations
-
Implicit conversions
2.2 Key Metrics to Evaluate
-
CPU Time – High computation usage
-
Duration – Total time including waits
-
Logical Reads – Memory pressure
-
Physical Reads – Disk I/O
-
Execution Count – High frequency = high impact
-
Wait Stats – Resource bottlenecks
2.3 Execution Plan Analysis
-
Capture the Actual Execution Plan
-
Look for:
-
Table/Clustered Index Scans
-
Key Lookups
-
Sort Operations
-
Spools
-
Implicit Conversions
-
-
Focus on high-cost operators first
Section 3: Eliminating Expensive Queries
3.1 Query Rewriting Techniques
Reduce unnecessary columns:
-- Before
SELECT * FROM Orders WHERE OrderDate > '2023-01-01';
-- After
SELECT OrderID, CustomerID, OrderDate
FROM Orders
WHERE OrderDate > '2023-01-01';
Avoid nested views:
-- Before
SELECT * FROM vw_OrderDetails WHERE Status = 'Shipped';
-- After
SELECT o.OrderID, o.OrderDate, c.CustomerName
FROM Orders o
JOIN Customers c ON o.CustomerID = c.CustomerID
WHERE o.Status = 'Shipped';
3.2 Index Optimization Strategies
Create missing indexes:
-- Review missing indexes
SELECT * FROM sys.dm_db_missing_index_details;
-- Example index
CREATE INDEX IX_Orders_Status_Date
ON Orders(Status, OrderDate)
INCLUDE (CustomerID, TotalAmount);
Maintain indexes:
-- Rebuild fragmented index
ALTER INDEX IX_Orders_CustomerID ON Orders REBUILD;
-- Update outdated statistics
UPDATE STATISTICS Orders WITH FULLSCAN;
3.3 Parameterization & Plan Reuse
Fix parameter sniffing:
-- Option 1
SELECT * FROM Orders
WHERE OrderDate >= @StartDate
OPTION (OPTIMIZE FOR (@StartDate = '2023-01-01'));
-- Option 2
DECLARE @LocalStartDate DATETIME = @StartDate;
SELECT * FROM Orders
WHERE OrderDate >= @LocalStartDate;
3.4 Batch Processing
Avoid massive single-batch operations:
-- Batched delete
WHILE EXISTS (SELECT 1 FROM OrderHistory WHERE OrderDate < '2020-01-01')
BEGIN
DELETE TOP (5000) FROM OrderHistory
WHERE OrderDate < '2020-01-01';
WAITFOR DELAY '00:00:01';
END
Section 4: Proactive Monitoring Strategies
4.1 Alerts for Expensive Queries
-- Create alert for long-running queries
EXEC msdb.dbo.sp_add_alert
@name = N'LongRunningQueries',
@message_id = 0,
@severity = 0,
@enabled = 1,
@delay_between_responses = 60,
@include_event_description_in = 1,
@condition_name = N'SQLServer:SQL Statistics|Batch Requests/sec|>|100',
@job_id = N'00000000-0000-0000-0000-000000000000';
4.2 Query Store Best Practices
-
Enable Query Store:
ALTER DATABASE YourDatabase SET QUERY_STORE = ON;
-
Monitor top regressions
-
Configure storage limits
-
Use forced plans carefully
4.3 Regular Performance Health Checks
-
Weekly:
-
Top queries by CPU/IO/frequency
-
-
Monthly:
-
Index rebuilds/statistics updates
-
-
Quarterly:
-
Benchmark regression reports
-
Section 5: When to Consider Architectural Changes
Sometimes tuning a query isn’t enough. Consider:
5.1 Database Design Changes
-
Partitioning
-
Denormalization
-
Columnstore Indexes
5.2 Application Adjustments
-
Add caching
-
Use reporting replicas
-
Read/write splitting
5.3 Infrastructure Upgrades
-
More RAM
-
SSDs/NVMe
-
More CPU cores
Conclusion
Eliminating expensive queries in SQL Server means:
-
Proactively monitoring with DMVs and Query Store
-
Analyzing execution plans for root causes
-
Tuning intelligently with rewrites and indexing
-
Documenting and validating every change
-
Evolving continuously as workloads grow
Database performance isn’t one-and-done it’s an ongoing journey. By implementing the strategies above, you’ll ensure your SQL Server environment stays fast, efficient, and ready for whatever workload comes next.
Comments
Post a Comment