Which component(s) does this affect?
Problem Statement
Grid fetch is slow on slow machines because the query do things that could be avoided
Proposed Solution
Move from
Current query
DECLARE @rangeStart DATETIME = getdate()-20;
DECLARE @rangeEnd DATETIME = getdate()-10;
WITH plan_agg AS (
SELECT
rs.plan_id,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu_us,
SUM(rs.avg_duration * rs.count_executions) AS total_duration_us,
SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads,
SUM(rs.avg_logical_io_writes * rs.count_executions) AS total_writes,
SUM(rs.avg_physical_io_reads * rs.count_executions) AS total_physical_reads,
SUM(rs.avg_query_max_used_memory * rs.count_executions) AS total_memory_pages,
SUM(rs.count_executions) AS total_executions,
MAX(rs.last_execution_time) AS last_execution_time
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi on rs.runtime_stats_interval_id=rsi.runtime_stats_interval_id
WHERE rsi.start_time >= @rangeStart AND rsi.end_time < @rangeEnd
GROUP BY rs.plan_id
),
ranked AS (
SELECT
p.query_id,
pa.plan_id,
pa.total_cpu_us,
pa.total_duration_us,
pa.total_reads,
pa.total_writes,
pa.total_physical_reads,
pa.total_memory_pages,
pa.total_executions,
pa.last_execution_time,
CASE WHEN pa.total_executions > 0
THEN pa.total_cpu_us / pa.total_executions ELSE 0 END AS avg_cpu_us,
CASE WHEN pa.total_executions > 0
THEN pa.total_duration_us / pa.total_executions ELSE 0 END AS avg_duration_us,
CASE WHEN pa.total_executions > 0
THEN pa.total_reads / pa.total_executions ELSE 0 END AS avg_reads,
CASE WHEN pa.total_executions > 0
THEN pa.total_writes / pa.total_executions ELSE 0 END AS avg_writes,
CASE WHEN pa.total_executions > 0
THEN pa.total_physical_reads / pa.total_executions ELSE 0 END AS avg_physical_reads,
CASE WHEN pa.total_executions > 0
THEN pa.total_memory_pages / pa.total_executions ELSE 0 END AS avg_memory_pages,
ROW_NUMBER() OVER (PARTITION BY p.query_id ORDER BY pa.total_duration_us DESC) AS rn
FROM plan_agg pa
JOIN sys.query_store_plan p ON pa.plan_id = p.plan_id
WHERE p.query_plan IS NOT NULL
)
SELECT TOP (25)
r.query_id,
r.plan_id,
qt.query_sql_text,
CAST(p.query_plan AS nvarchar(max)) AS query_plan,
r.avg_cpu_us,
r.avg_duration_us,
r.avg_reads,
r.avg_writes,
r.avg_physical_reads,
r.avg_memory_pages,
r.total_executions,
CAST(r.total_cpu_us AS bigint),
CAST(r.total_duration_us AS bigint),
CAST(r.total_reads AS bigint),
CAST(r.total_writes AS bigint),
CAST(r.total_physical_reads AS bigint),
CAST(r.total_memory_pages AS bigint),
r.last_execution_time,
CONVERT(varchar(18), q.query_hash, 1),
CONVERT(varchar(18), p.query_plan_hash, 1),
CASE
WHEN q.object_id <> 0
THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id)
ELSE N''
END
FROM ranked r
JOIN sys.query_store_plan p ON r.plan_id = p.plan_id
JOIN sys.query_store_query q ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE 1 = 1 AND r.rn = 1
ORDER BY r.total_duration_us DESC
OPTION (LOOP JOIN);
New proposition
DECLARE @rangeStart DATETIME = getdate()-20;
DECLARE @rangeEnd DATETIME = getdate()-19;
drop table if exists #top_plans;
WITH plan_agg AS (
SELECT
rs.plan_id,
SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu_us,
SUM(rs.avg_duration * rs.count_executions) AS total_duration_us,
SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads,
SUM(rs.avg_logical_io_writes * rs.count_executions) AS total_writes,
SUM(rs.avg_physical_io_reads * rs.count_executions) AS total_physical_reads,
SUM(rs.avg_query_max_used_memory * rs.count_executions) AS total_memory_pages,
SUM(rs.count_executions) AS total_executions,
MAX(rs.last_execution_time) AS last_execution_time
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi on rs.runtime_stats_interval_id=rsi.runtime_stats_interval_id
WHERE rsi.start_time >= @rangeStart AND rsi.end_time < @rangeEnd
GROUP BY rs.plan_id
) ,
ranked AS (
SELECT
p.query_id,
pa.plan_id,
pa.total_cpu_us,
pa.total_duration_us,
pa.total_reads,
pa.total_writes,
pa.total_physical_reads,
pa.total_memory_pages,
pa.total_executions,
pa.last_execution_time,
CASE WHEN pa.total_executions > 0
THEN pa.total_cpu_us / pa.total_executions ELSE 0 END AS avg_cpu_us,
CASE WHEN pa.total_executions > 0
THEN pa.total_duration_us / pa.total_executions ELSE 0 END AS avg_duration_us,
CASE WHEN pa.total_executions > 0
THEN pa.total_reads / pa.total_executions ELSE 0 END AS avg_reads,
CASE WHEN pa.total_executions > 0
THEN pa.total_writes / pa.total_executions ELSE 0 END AS avg_writes,
CASE WHEN pa.total_executions > 0
THEN pa.total_physical_reads / pa.total_executions ELSE 0 END AS avg_physical_reads,
CASE WHEN pa.total_executions > 0
THEN pa.total_memory_pages / pa.total_executions ELSE 0 END AS avg_memory_pages,
ROW_NUMBER() OVER (PARTITION BY p.query_id ORDER BY pa.total_duration_us DESC) AS rn
FROM plan_agg pa
JOIN sys.query_store_plan p ON pa.plan_id = p.plan_id
--WHERE p.query_plan IS NOT NULL -- predicate on nvarchar(max) that lead to a slow implicit conversion
)
SELECT TOP (25)
r.query_id,
r.plan_id,
r.avg_cpu_us,
r.avg_duration_us,
r.avg_reads,
r.avg_writes,
r.avg_physical_reads,
r.avg_memory_pages,
r.total_executions,
CAST(r.total_cpu_us AS bigint) total_cpu_us,
CAST(r.total_duration_us AS bigint) total_duration_us,
CAST(r.total_reads AS bigint) total_reads,
CAST(r.total_writes AS bigint) total_writes,
CAST(r.total_physical_reads AS bigint) total_physical_reads,
CAST(r.total_memory_pages AS bigint) total_memory_pages,
r.last_execution_time
INTO
#top_plans
FROM ranked r
WHERE 1 = 1 AND r.rn = 1
ORDER BY r.total_duration_us DESC
SELECT
topplans.query_id,
topplans.plan_id,
qt.query_sql_text,
CAST(p.query_plan AS nvarchar(max)) AS query_plan,
topplans.avg_cpu_us,
topplans.avg_duration_us,
topplans.avg_reads,
topplans.avg_writes,
topplans.avg_physical_reads,
topplans.avg_memory_pages,
topplans.total_executions,
topplans.total_cpu_us,
topplans.total_duration_us,
topplans.total_reads,
topplans.total_writes,
topplans.total_physical_reads,
topplans.total_memory_pages,
topplans.last_execution_time,
CONVERT(varchar(18), q.query_hash, 1) query_hash,
CONVERT(varchar(18), p.query_plan_hash, 1) query_plan_hash,
CASE
WHEN q.object_id <> 0
THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id)
ELSE N''
END objectname
FROM #top_plans topplans
JOIN sys.query_store_plan p ON topplans.plan_id = p.plan_id
JOIN sys.query_store_query q ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id;
What was done :
- remove filter
WHERE p.query_plan IS NOT NULL than generate a very slow implicit convert
- use a temp table to store top plans then join with query_text and return join for plan
- remove hint LOOP JOIN (3x faster...for some tests)
Results :
Use Case
- Slow machines or slow cloud
Alternatives Considered
No response
Which component(s) does this affect?
Problem Statement
Grid fetch is slow on slow machines because the query do things that could be avoided
Proposed Solution
Move from
Current query
New proposition
What was done :
WHERE p.query_plan IS NOT NULLthan generate a very slow implicit convertResults :
Use Case
Alternatives Considered
No response