A DBA should be able to review every statement SQLInsight sends to a monitored server before granting it access. "SQL Scripts/<rule-id>.sql" holds, per rule (82 built-in, 16 community), a Danish header (what and why, how C# judges the data, thresholds with config keys, severity, exact GRANTs, versions, collector file and member) and the collector's SQL verbatim, every version variant as its own batch. "_collectors/<Collector>.sql" holds each collector's queries once. The only statement that is not strictly read-only is documented in the scripts: the deadlock collector's session temp table (#system_health). Inline SQL in the collectors moved to internal constants and Build*Sql methods (no text changed), so SqlScriptTests can check that the scripts still contain the SQL the code sends, that every rule has a script, that every collector query is registered, and that nothing writes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
130 lines
8.4 KiB
Transact-SQL
130 lines
8.4 KiB
Transact-SQL
-- ===================================================================================================================
|
|
-- 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
|