-- =================================================================================================================== -- SQLI-CPU-002: Sætning kører næsten uafbrudt -- =================================================================================================================== -- Regel-id: SQLI-CPU-002 -- Titel: Sætning kører næsten uafbrudt -- Alvor: Medium -- Versioner: SQL Server 2005 og nyere. -- -- Hvad tjekkes, og hvorfor: -- Om en sætning i praksis holder en CPU-kerne beskæftiget hele tiden. -- -- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): -- For hver sætning (query_hash; på 2005 sql_handle + offsets) i top SQL: aktivitet = total_worker_time / -- (last_execution_time - creation_time) i procent af én kerne. Fund (op til 5, højeste først) for sætninger, der -- har kørt inden for de seneste 15 minutter, har ligget mindst 1 time i plan cache og har aktivitet over grænsen. -- -- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): -- SQLInsight:TopSql:ActivityThresholdPercent, standard 50 (%); mindst 1 time i plan cache -- (ConstantActivityRule.MinimumLifetime, fast i koden). -- -- 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+ -- -- Data kommer fra (collector, fil : medlemmer, interval): -- QueryStatsCollector -- Fil: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs -- Medlemmer: Db.StartTimeSql(version), QueryStatsCollector.Sql2008 via SqlFor(version, since: null), -- QueryStatsCollector.Sql2005 via SqlFor(version, since: null), QueryStatsCollector.ServerTimeSql -- Interval: 5 min som standard (SQLInsight:CollectorIntervals:query-stats) -- Delt script: SQL Scripts/_collectors/QueryStatsCollector.sql -- Regelkode: src/SQLInsight.Rules/Performance/OperationRules.cs : ConstantActivityRule.Evaluate -- -- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter. -- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch -- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet. -- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den -- database, du vil se. -- =================================================================================================================== -- ------------------------------------------------------------------------------------------------------------------- -- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse -- ------------------------------------------------------------------------------------------------------------------- -- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner -- Kilde: SqlConnectionFactory.SessionSettings -- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; GO -- ------------------------------------------------------------------------------------------------------------------- -- QueryStatsCollector: Top SQL: de tungeste sætninger (top 50 efter CPU, varighed, læsninger og antal) blandt dem, -- der har kørt siden forrige måling. Ingen planer, tekst afkortet. -- ------------------------------------------------------------------------------------------------------------------- -- Variant: SQL Server 2008 og nyere: starttidspunkt -- Kilde: QueryStatsCollector.StartTimeSql(2008+) -- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info) GO -- Variant: SQL Server 2005: starttidspunkt -- Kilde: QueryStatsCollector.StartTimeSql(2005) -- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2) GO -- Variant: SQL Server 2008 og nyere (første kørsel: de seneste 15 minutter) -- Kilde: QueryStatsCollector.SqlFor(2008+) -- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2008 via SqlFor(version, since: null) WITH s AS ( SELECT qs.query_hash AS k, SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed, SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes, MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec FROM sys.dm_exec_query_stats qs GROUP BY qs.query_hash HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())), t AS ( SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY cpu DESC) a UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY elapsed DESC) b UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY lreads DESC) c UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY execs DESC) d) SELECT CONVERT(varchar(20), s.k, 1), s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec, DB_NAME(COALESCE(st.dbid, pa.dbid)), CAST(SUBSTRING(st.text, r.statement_start_offset / 2 + 1, CASE WHEN r.statement_end_offset = -1 THEN 4000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(4000)), GETDATE() FROM s JOIN t ON t.k = s.k OUTER APPLY (SELECT TOP (1) q.sql_handle, q.plan_handle, q.statement_start_offset, q.statement_end_offset FROM sys.dm_exec_query_stats q WHERE q.query_hash = s.k ORDER BY q.last_execution_time DESC) r OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa GO -- Variant: SQL Server 2005 (første kørsel: de seneste 15 minutter) -- Kilde: QueryStatsCollector.SqlFor(2005) -- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2005 via SqlFor(version, since: null) WITH s AS ( SELECT qs.sql_handle AS h, qs.statement_start_offset AS so, qs.statement_end_offset AS eo, SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed, SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes, MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec FROM sys.dm_exec_query_stats qs GROUP BY qs.sql_handle, qs.statement_start_offset, qs.statement_end_offset HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())), t AS ( SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY cpu DESC) a UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY elapsed DESC) b UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY lreads DESC) c UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY execs DESC) d) SELECT sys.fn_varbintohexstr(s.h) + ':' + CAST(s.so AS varchar(12)) + ':' + CAST(s.eo AS varchar(12)), s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec, DB_NAME(COALESCE(st.dbid, pa.dbid)), CAST(SUBSTRING(st.text, s.so / 2 + 1, CASE WHEN s.eo = -1 THEN 4000 ELSE (s.eo - s.so) / 2 + 1 END) AS nvarchar(4000)), GETDATE() FROM s JOIN t ON t.h = s.h AND t.so = s.so AND t.eo = s.eo OUTER APPLY sys.dm_exec_sql_text(s.h) st OUTER APPLY (SELECT TOP (1) q.plan_handle FROM sys.dm_exec_query_stats q WHERE q.sql_handle = s.h AND q.statement_start_offset = s.so AND q.statement_end_offset = s.eo) r OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa GO -- Variant: Alle versioner, kun når ingen sætning har kørt siden sidst -- Kilde: QueryStatsCollector.ServerTimeSql -- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.ServerTimeSql SELECT GETDATE() GO