-- =================================================================================================================== -- Collector: QueryStoreCollector ("query-store") -- =================================================================================================================== -- Hvad: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage og -- forcerede planer, der fejler. -- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs -- Databaser: Fra 1.3 kun de databaser, der er valgt under Servere og databaser (systemdatabaserne altid; se -- _collectors/DatabaseDiscovery.sql). Fravalgte databaser sorteres fra i koden efter forespørgslen; SQL'en er uændret. -- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store) -- Versioner: SQL Server 2016 og nyere -- -- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): -- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019 -- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE) -- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+ -- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data -- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; giver IKKE adgang til data -- Før 2014 i stedet, i hver brugerdatabase: CREATE USER [agent] FOR LOGIN [agent]; -- -- Bruges af: SQLI-QS-001, SQLI-QS-002, SQLI-QS-003 -- Read-only: Ja: kun SELECT samt lokale variabler, SET-sessionsindstillinger. Intet ændrer data, indstillinger -- eller objekter. -- Kørsel: Kør i SSMS som agentens login. Hver variant er sin egen batch (GO); en variant for en anden SQL Server-version -- end din kan fejle - det er forventet. Agenten kører altid med sessionsindstillingerne i _collectors/InstanceProbe.sql. -- =================================================================================================================== -- Variant: SQL Server 2016 og nyere -- Kilde: QueryStoreCollector.DatabasesSql -- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.DatabasesSql SELECT name FROM sys.databases WHERE is_query_store_on = 1 AND state = 0 AND database_id > 4 GO -- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb) -- Kilde: QueryStoreCollector.BuildStatsSql -- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildStatsSql(database) DECLARE @now datetimeoffset = SYSDATETIMEOFFSET(); DECLARE @recent datetimeoffset = DATEADD(hour, -24, @now); DECLARE @history datetimeoffset = DATEADD(day, -8, @now); WITH stats AS ( SELECT p.query_id, p.plan_id, CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END AS is_recent, SUM(rs.count_executions) AS execs, SUM(rs.avg_duration * rs.count_executions) AS total_duration, SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu, SUM(rs.avg_logical_io_reads * rs.count_executions) AS total_reads, MAX(rs.stdev_duration) AS max_stdev_duration, MAX(rs.max_duration) AS max_duration, MIN(rs.min_duration) AS min_duration, MAX(rs.last_execution_time) AS last_execution FROM [msdb].sys.query_store_runtime_stats rs JOIN [msdb].sys.query_store_runtime_stats_interval i ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id JOIN [msdb].sys.query_store_plan p ON p.plan_id = rs.plan_id WHERE i.start_time >= @history GROUP BY p.query_id, p.plan_id, CASE WHEN i.start_time >= @recent THEN 1 ELSE 0 END ), top_queries AS ( SELECT TOP (100) query_id FROM stats GROUP BY query_id ORDER BY SUM(total_duration) DESC ) SELECT s.query_id, s.plan_id, s.is_recent, s.execs, s.total_duration, s.total_cpu, s.total_reads, s.max_stdev_duration, s.max_duration, s.min_duration, CAST(SWITCHOFFSET(s.last_execution, '+00:00') AS datetime2(0)), p.is_forced_plan, LEFT(qt.query_sql_text, 2000), OBJECT_SCHEMA_NAME(q.object_id, DB_ID(N'msdb')) + N'.' + OBJECT_NAME(q.object_id, DB_ID(N'msdb')) FROM stats s JOIN top_queries t ON t.query_id = s.query_id JOIN [msdb].sys.query_store_plan p ON p.plan_id = s.plan_id JOIN [msdb].sys.query_store_query q ON q.query_id = s.query_id JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id; GO -- Variant: SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for msdb) -- Kilde: QueryStoreCollector.BuildFailedForcedSql -- Kode: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs : QueryStoreCollector.BuildFailedForcedSql(database) SELECT p.query_id, p.plan_id, p.force_failure_count, p.last_force_failure_reason_desc, LEFT(qt.query_sql_text, 2000) FROM [msdb].sys.query_store_plan p JOIN [msdb].sys.query_store_query q ON q.query_id = p.query_id JOIN [msdb].sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id WHERE p.is_forced_plan = 1 AND p.force_failure_count > 0; GO