From 444d927da33ef0cb031ff7c426e6e324f04ab9e6 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:49:17 -0500 Subject: [PATCH] Add optional query text and plan collection flags to schedule table (#337) Adds collect_query and collect_plan bit columns to config.collection_schedule (NOT NULL, DEFAULT 1) so users can disable text/plan storage per collector to reduce data volume. Collectors 08, 09, 10 read the flags and return NULL when disabled. Includes ALTER TABLE for existing installations. Clean install tested on sql2016, verified flags work correctly (enabled: data collected, disabled: NULLs stored). Co-Authored-By: Claude Opus 4.6 --- install/01_install_database.sql | 45 ++++++++++++++++++++++++++ install/08_collect_query_stats.sql | 22 ++++++++++++- install/09_collect_query_store.sql | 45 +++++++++++++++++++++++--- install/10_collect_procedure_stats.sql | 32 ++++++++++++++++-- 4 files changed, 136 insertions(+), 8 deletions(-) diff --git a/install/01_install_database.sql b/install/01_install_database.sql index db141d7..a435add 100644 --- a/install/01_install_database.sql +++ b/install/01_install_database.sql @@ -274,6 +274,10 @@ BEGIN DEFAULT 5, retention_days integer NOT NULL DEFAULT 30, + collect_query bit NOT NULL + DEFAULT CONVERT(bit, 'true'), + collect_plan bit NOT NULL + DEFAULT CONVERT(bit, 'true'), [description] nvarchar(500) NULL, created_date datetime2(7) NOT NULL DEFAULT SYSDATETIME(), @@ -317,6 +321,47 @@ BEGIN PRINT 'Created config.collection_schedule table'; END; +/* +Add collect_query and collect_plan columns for existing installations +Controls whether collectors store query text and execution plans +Both default to enabled (1) for backwards compatibility +*/ +IF NOT EXISTS +( + SELECT + 1/0 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'config.collection_schedule') + AND name = N'collect_query' +) +BEGIN + ALTER TABLE + config.collection_schedule + ADD collect_query bit NOT NULL + CONSTRAINT DF_collection_schedule_collect_query + DEFAULT CONVERT(bit, 'true'); + + PRINT 'Added collect_query column to config.collection_schedule'; +END; + +IF NOT EXISTS +( + SELECT + 1/0 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'config.collection_schedule') + AND name = N'collect_plan' +) +BEGIN + ALTER TABLE + config.collection_schedule + ADD collect_plan bit NOT NULL + CONSTRAINT DF_collection_schedule_collect_plan + DEFAULT CONVERT(bit, 'true'); + + PRINT 'Added collect_plan column to config.collection_schedule'; +END; + /* Critical issues table Logs significant performance problems detected by collectors and analysis procedures diff --git a/install/08_collect_query_stats.sql b/install/08_collect_query_stats.sql index 6d2cb2b..0d62c24 100644 --- a/install/08_collect_query_stats.sql +++ b/install/08_collect_query_stats.sql @@ -153,6 +153,19 @@ BEGIN RAISERROR(N'Collecting queries executed since %s', 0, 1, @cutoff_time_string) WITH NOWAIT; END; + /* + Read collection flags for query text and plans + */ + DECLARE + @collect_query bit = 1, + @collect_plan bit = 1; + + SELECT + @collect_query = cs.collect_query, + @collect_plan = cs.collect_plan + FROM config.collection_schedule AS cs + WHERE cs.collector_name = N'query_stats_collector'; + /* Collect query statistics directly from DMV Only collects queries executed since last collection @@ -255,6 +268,8 @@ BEGIN max_spills = qs.max_spills, query_text = CASE + WHEN @collect_query = 0 + THEN NULL WHEN qs.statement_start_offset = 0 AND qs.statement_end_offset = -1 THEN st.text @@ -272,7 +287,12 @@ BEGIN ) / 2 + 1 ) END, - query_plan_text = tqp.query_plan + query_plan_text = + CASE + WHEN @collect_plan = 1 + THEN tqp.query_plan + ELSE NULL + END FROM sys.dm_exec_query_stats AS qs OUTER APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st OUTER APPLY diff --git a/install/09_collect_query_store.sql b/install/09_collect_query_store.sql index 7505695..6794ea9 100644 --- a/install/09_collect_query_store.sql +++ b/install/09_collect_query_store.sql @@ -200,6 +200,19 @@ BEGIN RAISERROR(@debug_msg, 0, 1) WITH NOWAIT; END; + /* + Read collection flags for query text and plans + */ + DECLARE + @collect_query bit = 1, + @collect_plan bit = 1; + + SELECT + @collect_query = cs.collect_query, + @collect_plan = cs.collect_plan + FROM config.collection_schedule AS cs + WHERE cs.collector_name = N'query_store_collector'; + /* Create temp table to hold Query Store data from all databases */ @@ -408,8 +421,20 @@ BEGIN WHEN q.object_id > 0 AND o.object_id IS NOT NULL THEN o.object_name - END, - query_sql_text = qt.query_sql_text, + END,'; + + IF @collect_query = 1 + BEGIN + SET @sql += N' + query_sql_text = qt.query_sql_text,'; + END; + ELSE + BEGIN + SET @sql += N' + query_sql_text = NULL,'; + END; + + SET @sql += N' query_hash = q.query_hash, count_executions = rs.count_executions, avg_duration = rs.avg_duration, @@ -501,8 +526,20 @@ BEGIN is_forced_plan = p.is_forced_plan, p.force_failure_count, p.last_force_failure_reason_desc, - p.compatibility_level, - query_plan_text = CONVERT(nvarchar(max), p.query_plan), + p.compatibility_level,'; + + IF @collect_plan = 1 + BEGIN + SET @sql += N' + query_plan_text = CONVERT(nvarchar(max), p.query_plan),'; + END; + ELSE + BEGIN + SET @sql += N' + query_plan_text = NULL,'; + END; + + SET @sql += N' compilation_metrics = ( SELECT diff --git a/install/10_collect_procedure_stats.sql b/install/10_collect_procedure_stats.sql index d2b95d7..006616a 100644 --- a/install/10_collect_procedure_stats.sql +++ b/install/10_collect_procedure_stats.sql @@ -153,6 +153,17 @@ BEGIN RAISERROR(N'Collecting procedure stats with cutoff time: %s', 0, 1, @cutoff_time_string) WITH NOWAIT; END; + /* + Read collection flag for plans + */ + DECLARE + @collect_plan bit = 1; + + SELECT + @collect_plan = cs.collect_plan + FROM config.collection_schedule AS cs + WHERE cs.collector_name = N'procedure_stats_collector'; + /* Collect procedure, trigger, and function statistics Single query with UNION ALL to collect from all three DMVs @@ -223,7 +234,12 @@ BEGIN total_spills = ps.total_spills, min_spills = ps.min_spills, max_spills = ps.max_spills, - query_plan_text = CONVERT(nvarchar(max), tqp.query_plan) + query_plan_text = + CASE + WHEN @collect_plan = 1 + THEN CONVERT(nvarchar(max), tqp.query_plan) + ELSE NULL + END FROM sys.dm_exec_procedure_stats AS ps OUTER APPLY sys.dm_exec_text_query_plan @@ -385,7 +401,12 @@ BEGIN total_spills = ts.total_spills, min_spills = ts.min_spills, max_spills = ts.max_spills, - query_plan_text = CONVERT(nvarchar(max), tqp.query_plan) + query_plan_text = + CASE + WHEN @collect_plan = 1 + THEN CONVERT(nvarchar(max), tqp.query_plan) + ELSE NULL + END FROM sys.dm_exec_trigger_stats AS ts CROSS APPLY sys.dm_exec_sql_text(ts.sql_handle) AS st OUTER APPLY @@ -444,7 +465,12 @@ BEGIN total_spills = NULL, min_spills = NULL, max_spills = NULL, - query_plan_text = CONVERT(nvarchar(max), tqp.query_plan) + query_plan_text = + CASE + WHEN @collect_plan = 1 + THEN CONVERT(nvarchar(max), tqp.query_plan) + ELSE NULL + END FROM sys.dm_exec_function_stats AS fs OUTER APPLY sys.dm_exec_text_query_plan