Last active
July 24, 2025 21:06
-
-
Save JosiahSiegel/85c4a59068b81d9715da4b3a2b8b715e to your computer and use it in GitHub Desktop.
Azure SQL Database: Identify Query Issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DECLARE | |
@last_execution_time DATETIME = DATEADD(MINUTE, -60, GETUTCDATE()) | |
;WITH FilteredRuntimeStats AS ( | |
SELECT | |
rs.runtime_stats_id, | |
rs.plan_id, | |
rs.last_execution_time, | |
rs.count_executions, | |
rs.avg_cpu_time, | |
rs.avg_duration, | |
rs.avg_physical_io_reads, | |
rs.avg_logical_io_reads, | |
rs.avg_query_max_used_memory | |
FROM sys.query_store_runtime_stats rs | |
WHERE rs.last_execution_time >= @last_execution_time | |
), | |
PlanMetrics AS ( | |
SELECT | |
plan_id, | |
COUNT(DISTINCT runtime_stats_id) as execution_count, | |
AVG(avg_cpu_time) as avg_cpu, | |
MIN(avg_cpu_time) as min_cpu, | |
MAX(avg_cpu_time) as max_cpu, | |
STDEV(avg_cpu_time) as stdev_cpu, | |
AVG(avg_duration) as avg_duration, | |
AVG(avg_physical_io_reads) as avg_physical_io, | |
AVG(avg_logical_io_reads) as avg_logical_io, | |
AVG(avg_query_max_used_memory) as avg_memory, | |
SUM(avg_cpu_time * count_executions) as total_cpu_time, | |
SUM(avg_duration * count_executions) as total_duration_time, | |
SUM(avg_physical_io_reads * count_executions) as total_physical_io, | |
SUM(avg_logical_io_reads * count_executions) as total_logical_io, | |
SUM(count_executions) as total_executions, | |
MAX(last_execution_time) as last_execution | |
FROM FilteredRuntimeStats | |
GROUP BY plan_id | |
), | |
QueryPlans AS ( | |
SELECT | |
q.query_id, | |
q.object_id, | |
q.query_text_id, | |
p.plan_id, | |
COUNT(p.plan_id) OVER (PARTITION BY q.query_id) as plan_count | |
FROM sys.query_store_query q | |
INNER JOIN sys.query_store_plan p ON q.query_id = p.query_id | |
), | |
BaseData AS ( | |
SELECT | |
qp.query_id, | |
qp.object_id, | |
qp.query_text_id, | |
qt.query_sql_text, | |
qp.plan_count, | |
pm.execution_count, | |
pm.avg_cpu, | |
pm.min_cpu, | |
pm.max_cpu, | |
pm.stdev_cpu, | |
pm.avg_duration, | |
pm.avg_physical_io, | |
pm.avg_logical_io, | |
pm.avg_memory, | |
pm.total_cpu_time, | |
pm.total_duration_time, | |
pm.total_physical_io, | |
pm.total_logical_io, | |
pm.total_executions, | |
pm.last_execution, | |
CASE WHEN pm.min_cpu > 0 THEN pm.max_cpu / pm.min_cpu ELSE NULL END as cpu_variance_ratio, | |
CASE WHEN pm.avg_cpu > 0 THEN pm.stdev_cpu / pm.avg_cpu ELSE NULL END as coefficient_of_variation, | |
-- Calculate wait time ratio | |
CASE WHEN pm.avg_cpu > 0 THEN (pm.avg_duration - pm.avg_cpu) / pm.avg_cpu ELSE NULL END as wait_ratio | |
FROM QueryPlans qp | |
INNER JOIN PlanMetrics pm ON qp.plan_id = pm.plan_id | |
INNER JOIN sys.query_store_query_text qt ON qp.query_text_id = qt.query_text_id | |
WHERE | |
qp.plan_count > 1 | |
OR pm.stdev_cpu / NULLIF(pm.avg_cpu, 0) > 0.5 | |
OR pm.total_cpu_time > 1000000 | |
OR pm.total_logical_io > 10000000 | |
), | |
RankedQueries AS ( | |
SELECT | |
*, | |
CASE | |
WHEN object_id = 0 THEN 'ADHOC_' + CAST(query_text_id as varchar(50)) | |
ELSE CAST(object_id as varchar(50)) | |
END as group_key, | |
ROW_NUMBER() OVER ( | |
PARTITION BY | |
CASE | |
WHEN object_id = 0 THEN 'ADHOC_' + CAST(query_text_id as varchar(50)) | |
ELSE CAST(object_id as varchar(50)) | |
END | |
ORDER BY total_cpu_time DESC | |
) as query_rank | |
FROM BaseData | |
), | |
AggregatedIssues AS ( | |
SELECT | |
group_key, | |
MAX(object_id) as object_id, | |
MAX(query_text_id) as query_text_id, | |
MAX(query_sql_text) as query_sql_text, | |
MAX(CASE WHEN query_rank = 1 THEN query_id END) as query_id_1, | |
MAX(CASE WHEN query_rank = 2 THEN query_id END) as query_id_2, | |
MAX(CASE WHEN query_rank = 3 THEN query_id END) as query_id_3, | |
COUNT(DISTINCT query_id) as query_count, | |
SUM(total_executions) as total_executions, | |
MAX(last_execution) as last_execution, | |
AVG(avg_cpu) as avg_cpu_time, | |
AVG(avg_duration) as avg_duration_time, | |
AVG(avg_physical_io) as avg_physical_io, | |
AVG(avg_logical_io) as avg_logical_io, | |
AVG(avg_memory) as avg_memory, | |
AVG(wait_ratio) as avg_wait_ratio, | |
SUM(total_cpu_time) as total_cpu_time, | |
SUM(total_duration_time) as total_duration_time, | |
SUM(total_physical_io) as total_physical_io, | |
SUM(total_logical_io) as total_logical_io, | |
MAX(plan_count) as max_plan_count, | |
MAX(coefficient_of_variation) as max_cv, | |
CONCAT( | |
-- Primary issue | |
CASE | |
WHEN SUM(total_cpu_time) > 10000000 THEN | |
'CRITICAL: CPU ' + CAST(CAST(SUM(total_cpu_time)/1000000.0 as decimal(10,1)) as varchar) + 's' | |
WHEN SUM(total_logical_io) > 100000000 THEN | |
'CRITICAL: IO ' + CAST(CAST(SUM(total_logical_io)/1000000.0 as decimal(10,1)) as varchar) + 'M' | |
WHEN MAX(plan_count) > 5 THEN | |
'HIGH: ' + CAST(MAX(plan_count) as varchar) + ' plans' | |
WHEN MAX(coefficient_of_variation) > 2 THEN | |
'HIGH: Variance (CoV=' + CAST(CAST(MAX(coefficient_of_variation) as decimal(5,2)) as varchar) + ')' | |
WHEN SUM(total_cpu_time) > 1000000 THEN | |
'MEDIUM: CPU ' + CAST(CAST(SUM(total_cpu_time)/1000000.0 as decimal(10,1)) as varchar) + 's' | |
WHEN SUM(total_logical_io) > 10000000 THEN | |
'MEDIUM: IO ' + CAST(CAST(SUM(total_logical_io)/1000000.0 as decimal(10,1)) as varchar) + 'M' | |
WHEN MAX(plan_count) > 1 THEN | |
'LOW: ' + CAST(MAX(plan_count) as varchar) + ' plans' | |
WHEN MAX(coefficient_of_variation) > 0.5 THEN | |
'LOW: Variance (CoV=' + CAST(CAST(MAX(coefficient_of_variation) as decimal(5,2)) as varchar) + ')' | |
ELSE '' | |
END, | |
-- Root cause indicators | |
CASE | |
WHEN SUM(total_executions) > 100000 THEN | |
' | High frequency (' + CAST(SUM(total_executions) as varchar) + ' exec)' | |
ELSE '' | |
END, | |
CASE | |
WHEN AVG(avg_cpu) > 1000000 THEN | |
' | Complex query (' + CAST(CAST(AVG(avg_cpu)/1000000.0 as decimal(5,1)) as varchar) + 's/exec)' | |
ELSE '' | |
END, | |
CASE | |
WHEN SUM(total_physical_io) > 1000000 THEN | |
' | Missing index? (' + CAST(CAST(SUM(total_physical_io)/1000000.0 as decimal(10,1)) as varchar) + 'M phys reads)' | |
ELSE '' | |
END, | |
CASE | |
WHEN AVG(avg_memory) > 1024 THEN | |
' | Memory intensive (' + CAST(CAST(AVG(avg_memory)/1024.0 as decimal(10,1)) as varchar) + 'MB)' | |
ELSE '' | |
END, | |
CASE | |
WHEN AVG(wait_ratio) > 2 THEN | |
' | Blocking/waits (wait ' + CAST(CAST(AVG(wait_ratio) as decimal(5,1)) as varchar) + 'x CPU)' | |
ELSE '' | |
END, | |
CASE | |
WHEN MAX(plan_count) > 1 AND MAX(coefficient_of_variation) > 0.5 THEN | |
' | Param sniffing' | |
ELSE '' | |
END, | |
-- Anti-patterns | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%CURSOR%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | Cursor' ELSE '' | |
END, | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%IF%@%' OR query_sql_text LIKE '%CASE%WHEN%@%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | Conditional' ELSE '' | |
END, | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%SELECT *%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | SELECT *' ELSE '' | |
END, | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%NOT IN%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | NOT IN' ELSE '' | |
END, | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%OR%OR%OR%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | Multiple ORs' ELSE '' | |
END, | |
CASE | |
WHEN MAX(CASE WHEN query_sql_text LIKE '%DISTINCT%' AND query_sql_text LIKE '%JOIN%' THEN 1 ELSE 0 END) > 0 | |
THEN ' | DISTINCT+JOIN' ELSE '' | |
END | |
) as issue_summary, | |
-- Resource impact-based severity scoring | |
CASE | |
-- Extreme resource consumption (90-100) | |
WHEN SUM(total_cpu_time) > 100000000 THEN 100 -- >100s CPU | |
WHEN SUM(total_logical_io) > 1000000000 THEN 95 -- >1B reads | |
WHEN SUM(total_cpu_time) > 50000000 THEN 90 -- >50s CPU | |
-- High resource consumption (70-89) | |
WHEN SUM(total_logical_io) > 500000000 THEN 85 -- >500M reads | |
WHEN SUM(total_cpu_time) > 10000000 THEN 80 -- >10s CPU | |
WHEN SUM(total_logical_io) > 100000000 THEN 75 -- >100M reads | |
WHEN SUM(total_cpu_time) > 5000000 THEN 70 -- >5s CPU | |
-- Moderate resource consumption with issues (50-69) | |
WHEN SUM(total_cpu_time) > 1000000 AND MAX(plan_count) > 5 THEN 65 | |
WHEN SUM(total_logical_io) > 10000000 AND MAX(plan_count) > 5 THEN 60 | |
WHEN SUM(total_cpu_time) > 1000000 AND MAX(coefficient_of_variation) > 2 THEN 55 | |
WHEN SUM(total_logical_io) > 10000000 AND MAX(coefficient_of_variation) > 2 THEN 50 | |
-- Lower resource consumption with issues (30-49) | |
WHEN SUM(total_cpu_time) > 1000000 THEN 45 -- >1s CPU | |
WHEN SUM(total_logical_io) > 10000000 THEN 40 -- >10M reads | |
WHEN MAX(plan_count) > 5 THEN 35 | |
WHEN MAX(coefficient_of_variation) > 2 THEN 30 | |
-- Minor issues (10-29) | |
WHEN MAX(plan_count) > 1 THEN 20 | |
WHEN MAX(coefficient_of_variation) > 0.5 THEN 10 | |
ELSE 0 | |
END as severity_score | |
FROM RankedQueries | |
GROUP BY group_key | |
) | |
SELECT TOP 100 | |
-- Query type column | |
CASE | |
WHEN object_id = 0 THEN 'Ad-hoc' | |
ELSE 'Stored Proc' | |
END as query_type, | |
-- Query/procedure identifier | |
CASE | |
WHEN object_id = 0 THEN | |
LEFT(REPLACE(REPLACE(query_sql_text, CHAR(13), ' '), CHAR(10), ' '), 80) + | |
CASE WHEN LEN(query_sql_text) > 80 THEN '...' ELSE '' END | |
WHEN object_id > 0 AND OBJECT_NAME(object_id) IS NULL THEN | |
'Dropped (id=' + CAST(object_id as varchar) + ')' | |
ELSE | |
OBJECT_NAME(object_id) | |
END as query_name, | |
query_text_id, | |
query_id_1, | |
query_id_2, | |
query_id_3, | |
query_count, | |
total_executions, | |
last_execution AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time' as last_execution_est, | |
CAST(avg_cpu_time/1000000.0 as DECIMAL(18,2)) as avg_cpu_seconds, | |
CAST(total_cpu_time/1000000.0 as DECIMAL(18,2)) as total_cpu_seconds, | |
CAST(total_logical_io/1000000.0 as DECIMAL(18,2)) as total_io_million_reads, | |
severity_score, | |
issue_summary as issues | |
FROM AggregatedIssues | |
WHERE issue_summary != '' | |
ORDER BY severity_score DESC, total_cpu_time DESC |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment