From 2284e7f082c0780cc6535ebc5fa8e09915c741bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Eggert=20Lundsteen=20Olsen?= Date: Fri, 2 Oct 2026 13:20:06 +0200 Subject: [PATCH] SQL Scripts: one reviewable script per rule A DBA should be able to review every statement SQLInsight sends to a monitored server before granting it access. "SQL Scripts/.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/.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) --- CONTRIBUTING.md | 5 +- SQL Scripts/COM-AX-001.sql | 74 ++++ SQL Scripts/COM-AX-002.sql | 75 ++++ SQL Scripts/COM-AX-003.sql | 74 ++++ SQL Scripts/COM-AX-004.sql | 74 ++++ SQL Scripts/COM-AX-005.sql | 74 ++++ SQL Scripts/COM-AX-006.sql | 74 ++++ SQL Scripts/COM-AX-007.sql | 75 ++++ SQL Scripts/COM-CFG-001.sql | 62 ++++ SQL Scripts/COM-DB-001.sql | 73 ++++ SQL Scripts/COM-FT-001.sql | 73 ++++ SQL Scripts/COM-FT-002.sql | 73 ++++ SQL Scripts/COM-FT-003.sql | 73 ++++ SQL Scripts/COM-FT-004.sql | 74 ++++ SQL Scripts/COM-FT-005.sql | 72 ++++ SQL Scripts/COM-SEC-001.sql | 62 ++++ SQL Scripts/COM-SEC-002.sql | 63 ++++ SQL Scripts/README.md | 230 ++++++++++++ SQL Scripts/SQLI-AG-001.sql | 85 +++++ SQL Scripts/SQLI-AG-002.sql | 84 +++++ SQL Scripts/SQLI-AG-003.sql | 83 +++++ SQL Scripts/SQLI-ANOM-001.sql | 130 +++++++ SQL Scripts/SQLI-BLK-001.sql | 102 ++++++ SQL Scripts/SQLI-CFG-001.sql | 145 ++++++++ SQL Scripts/SQLI-CFG-002.sql | 145 ++++++++ SQL Scripts/SQLI-CFG-003.sql | 142 ++++++++ SQL Scripts/SQLI-CFG-004.sql | 144 ++++++++ SQL Scripts/SQLI-CFG-005.sql | 89 +++++ SQL Scripts/SQLI-CFG-006.sql | 65 ++++ SQL Scripts/SQLI-CFG-007.sql | 66 ++++ SQL Scripts/SQLI-CFG-008.sql | 66 ++++ SQL Scripts/SQLI-CFG-009.sql | 91 +++++ SQL Scripts/SQLI-CFG-010.sql | 66 ++++ SQL Scripts/SQLI-CFG-011.sql | 66 ++++ SQL Scripts/SQLI-CFG-012.sql | 81 +++++ SQL Scripts/SQLI-CFG-013.sql | 121 +++++++ SQL Scripts/SQLI-CPU-001.sql | 77 ++++ SQL Scripts/SQLI-CPU-002.sql | 130 +++++++ SQL Scripts/SQLI-DB-001.sql | 222 ++++++++++++ SQL Scripts/SQLI-DB-002.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-003.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-004.sql | 224 ++++++++++++ SQL Scripts/SQLI-DB-005.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-006.sql | 247 +++++++++++++ SQL Scripts/SQLI-DB-007.sql | 225 ++++++++++++ SQL Scripts/SQLI-DB-008.sql | 225 ++++++++++++ SQL Scripts/SQLI-DB-009.sql | 225 ++++++++++++ SQL Scripts/SQLI-DB-010.sql | 56 +++ SQL Scripts/SQLI-DB-011.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-012.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-013.sql | 223 ++++++++++++ SQL Scripts/SQLI-DB-014.sql | 224 ++++++++++++ SQL Scripts/SQLI-DB-015.sql | 299 ++++++++++++++++ SQL Scripts/SQLI-DLK-001.sql | 79 +++++ SQL Scripts/SQLI-IDX-001.sql | 161 +++++++++ SQL Scripts/SQLI-IDX-002.sql | 160 +++++++++ SQL Scripts/SQLI-IDX-003.sql | 160 +++++++++ SQL Scripts/SQLI-IDX-004.sql | 158 +++++++++ SQL Scripts/SQLI-IO-001.sql | 92 +++++ SQL Scripts/SQLI-IO-002.sql | 90 +++++ SQL Scripts/SQLI-JOB-001.sql | 76 ++++ SQL Scripts/SQLI-JOB-002.sql | 101 ++++++ SQL Scripts/SQLI-LOG-001.sql | 118 +++++++ SQL Scripts/SQLI-LOG-002.sql | 118 +++++++ SQL Scripts/SQLI-LOG-003.sql | 117 +++++++ SQL Scripts/SQLI-LOG-004.sql | 118 +++++++ SQL Scripts/SQLI-LOG-005.sql | 116 +++++++ SQL Scripts/SQLI-LOG-006.sql | 117 +++++++ SQL Scripts/SQLI-MEM-001.sql | 137 ++++++++ SQL Scripts/SQLI-OPS-001.sql | 66 ++++ SQL Scripts/SQLI-PLAN-001.sql | 127 +++++++ SQL Scripts/SQLI-PLAN-002.sql | 126 +++++++ SQL Scripts/SQLI-PLAN-003.sql | 126 +++++++ SQL Scripts/SQLI-PLAN-004.sql | 126 +++++++ SQL Scripts/SQLI-PLAN-005.sql | 126 +++++++ SQL Scripts/SQLI-QS-001.sql | 116 +++++++ SQL Scripts/SQLI-QS-002.sql | 111 ++++++ SQL Scripts/SQLI-QS-003.sql | 115 ++++++ SQL Scripts/SQLI-REPL-001.sql | 88 +++++ SQL Scripts/SQLI-REPL-002.sql | 86 +++++ SQL Scripts/SQLI-REQ-001.sql | 93 +++++ SQL Scripts/SQLI-SEC-001.sql | 106 ++++++ SQL Scripts/SQLI-SEC-002.sql | 104 ++++++ SQL Scripts/SQLI-STAT-001.sql | 159 +++++++++ SQL Scripts/SQLI-STAT-002.sql | 222 ++++++++++++ SQL Scripts/SQLI-SVC-001.sql | 118 +++++++ SQL Scripts/SQLI-TEMP-001.sql | 105 ++++++ SQL Scripts/SQLI-TEMP-002.sql | 101 ++++++ SQL Scripts/SQLI-TEMP-003.sql | 103 ++++++ SQL Scripts/SQLI-TRAN-001.sql | 92 +++++ SQL Scripts/SQLI-TRAN-002.sql | 93 +++++ SQL Scripts/SQLI-VER-001.sql | 71 ++++ SQL Scripts/SQLI-VER-002.sql | 68 ++++ SQL Scripts/SQLI-WAIT-001.sql | 87 +++++ SQL Scripts/SQLI-WAIT-002.sql | 87 +++++ SQL Scripts/SQLI-WAIT-003.sql | 87 +++++ SQL Scripts/SQLI-WAIT-004.sql | 87 +++++ SQL Scripts/SQLI-WAIT-005.sql | 86 +++++ SQL Scripts/SQLI-WAIT-006.sql | 88 +++++ SQL Scripts/SQLI-WAIT-007.sql | 88 +++++ SQL Scripts/_collectors/ActivityCollector.sql | 58 ++++ SQL Scripts/_collectors/AgentJobCollector.sql | 43 +++ .../AvailabilityGroupCollector.sql | 49 +++ SQL Scripts/_collectors/BlockingCollector.sql | 68 ++++ .../_collectors/ConfigurationCollector.sql | 33 ++ SQL Scripts/_collectors/CpuCollector.sql | 42 +++ .../_collectors/CustomQueryCollector.sql | 40 +++ SQL Scripts/_collectors/DatabaseCollector.sql | 190 ++++++++++ SQL Scripts/_collectors/DeadlockCollector.sql | 44 +++ SQL Scripts/_collectors/ErrorLogCollector.sql | 82 +++++ SQL Scripts/_collectors/FileIoCollector.sql | 57 +++ SQL Scripts/_collectors/IndexCollector.sql | 123 +++++++ .../InstanceConfigurationCollector.sql | 82 +++++ SQL Scripts/_collectors/InstanceProbe.sql | 37 ++ .../_collectors/LongOperationCollector.sql | 32 ++ .../PerformanceCounterCollector.sql | 60 ++++ .../_collectors/PlanAnalysisCollector.sql | 91 +++++ .../_collectors/PlanCacheCollector.sql | 29 ++ .../_collectors/PrivilegeCollector.sql | 67 ++++ .../_collectors/QueryStatsCollector.sql | 94 +++++ .../_collectors/QueryStoreCollector.sql | 78 +++++ .../_collectors/ReplicationCollector.sql | 53 +++ SQL Scripts/_collectors/ServerCollector.sql | 84 +++++ .../_collectors/SuspectPageCollector.sql | 23 ++ SQL Scripts/_collectors/TempdbCollector.sql | 67 ++++ .../_collectors/TraceFlagCollector.sql | 23 ++ .../_collectors/WaitSamplingCollector.sql | 46 +++ .../_collectors/WaitStatsCollector.sql | 54 +++ .../Concurrency/ActivityCollector.cs | 26 +- .../Custom/CustomQueryCollector.cs | 15 +- .../Databases/DatabaseCollector.cs | 13 +- src/SQLInsight.Collectors/Db.cs | 11 +- .../Instance/ErrorLogCollector.cs | 20 +- .../InstanceConfigurationCollector.cs | 15 +- .../Instance/PrivilegeCollector.cs | 13 +- .../Instance/ReplicationCollector.cs | 2 +- .../Instance/ServerCollector.cs | 37 +- .../Instance/TraceFlagCollector.cs | 4 +- .../Performance/CpuCollector.cs | 5 +- .../Performance/QueryStatsCollector.cs | 5 +- .../SQLInsight.Collectors.Tests.csproj | 3 + .../SqlScripts/SqlScriptSources.cs | 328 ++++++++++++++++++ .../SqlScripts/SqlScriptTests.cs | 221 ++++++++++++ 143 files changed, 14230 insertions(+), 56 deletions(-) create mode 100644 SQL Scripts/COM-AX-001.sql create mode 100644 SQL Scripts/COM-AX-002.sql create mode 100644 SQL Scripts/COM-AX-003.sql create mode 100644 SQL Scripts/COM-AX-004.sql create mode 100644 SQL Scripts/COM-AX-005.sql create mode 100644 SQL Scripts/COM-AX-006.sql create mode 100644 SQL Scripts/COM-AX-007.sql create mode 100644 SQL Scripts/COM-CFG-001.sql create mode 100644 SQL Scripts/COM-DB-001.sql create mode 100644 SQL Scripts/COM-FT-001.sql create mode 100644 SQL Scripts/COM-FT-002.sql create mode 100644 SQL Scripts/COM-FT-003.sql create mode 100644 SQL Scripts/COM-FT-004.sql create mode 100644 SQL Scripts/COM-FT-005.sql create mode 100644 SQL Scripts/COM-SEC-001.sql create mode 100644 SQL Scripts/COM-SEC-002.sql create mode 100644 SQL Scripts/README.md create mode 100644 SQL Scripts/SQLI-AG-001.sql create mode 100644 SQL Scripts/SQLI-AG-002.sql create mode 100644 SQL Scripts/SQLI-AG-003.sql create mode 100644 SQL Scripts/SQLI-ANOM-001.sql create mode 100644 SQL Scripts/SQLI-BLK-001.sql create mode 100644 SQL Scripts/SQLI-CFG-001.sql create mode 100644 SQL Scripts/SQLI-CFG-002.sql create mode 100644 SQL Scripts/SQLI-CFG-003.sql create mode 100644 SQL Scripts/SQLI-CFG-004.sql create mode 100644 SQL Scripts/SQLI-CFG-005.sql create mode 100644 SQL Scripts/SQLI-CFG-006.sql create mode 100644 SQL Scripts/SQLI-CFG-007.sql create mode 100644 SQL Scripts/SQLI-CFG-008.sql create mode 100644 SQL Scripts/SQLI-CFG-009.sql create mode 100644 SQL Scripts/SQLI-CFG-010.sql create mode 100644 SQL Scripts/SQLI-CFG-011.sql create mode 100644 SQL Scripts/SQLI-CFG-012.sql create mode 100644 SQL Scripts/SQLI-CFG-013.sql create mode 100644 SQL Scripts/SQLI-CPU-001.sql create mode 100644 SQL Scripts/SQLI-CPU-002.sql create mode 100644 SQL Scripts/SQLI-DB-001.sql create mode 100644 SQL Scripts/SQLI-DB-002.sql create mode 100644 SQL Scripts/SQLI-DB-003.sql create mode 100644 SQL Scripts/SQLI-DB-004.sql create mode 100644 SQL Scripts/SQLI-DB-005.sql create mode 100644 SQL Scripts/SQLI-DB-006.sql create mode 100644 SQL Scripts/SQLI-DB-007.sql create mode 100644 SQL Scripts/SQLI-DB-008.sql create mode 100644 SQL Scripts/SQLI-DB-009.sql create mode 100644 SQL Scripts/SQLI-DB-010.sql create mode 100644 SQL Scripts/SQLI-DB-011.sql create mode 100644 SQL Scripts/SQLI-DB-012.sql create mode 100644 SQL Scripts/SQLI-DB-013.sql create mode 100644 SQL Scripts/SQLI-DB-014.sql create mode 100644 SQL Scripts/SQLI-DB-015.sql create mode 100644 SQL Scripts/SQLI-DLK-001.sql create mode 100644 SQL Scripts/SQLI-IDX-001.sql create mode 100644 SQL Scripts/SQLI-IDX-002.sql create mode 100644 SQL Scripts/SQLI-IDX-003.sql create mode 100644 SQL Scripts/SQLI-IDX-004.sql create mode 100644 SQL Scripts/SQLI-IO-001.sql create mode 100644 SQL Scripts/SQLI-IO-002.sql create mode 100644 SQL Scripts/SQLI-JOB-001.sql create mode 100644 SQL Scripts/SQLI-JOB-002.sql create mode 100644 SQL Scripts/SQLI-LOG-001.sql create mode 100644 SQL Scripts/SQLI-LOG-002.sql create mode 100644 SQL Scripts/SQLI-LOG-003.sql create mode 100644 SQL Scripts/SQLI-LOG-004.sql create mode 100644 SQL Scripts/SQLI-LOG-005.sql create mode 100644 SQL Scripts/SQLI-LOG-006.sql create mode 100644 SQL Scripts/SQLI-MEM-001.sql create mode 100644 SQL Scripts/SQLI-OPS-001.sql create mode 100644 SQL Scripts/SQLI-PLAN-001.sql create mode 100644 SQL Scripts/SQLI-PLAN-002.sql create mode 100644 SQL Scripts/SQLI-PLAN-003.sql create mode 100644 SQL Scripts/SQLI-PLAN-004.sql create mode 100644 SQL Scripts/SQLI-PLAN-005.sql create mode 100644 SQL Scripts/SQLI-QS-001.sql create mode 100644 SQL Scripts/SQLI-QS-002.sql create mode 100644 SQL Scripts/SQLI-QS-003.sql create mode 100644 SQL Scripts/SQLI-REPL-001.sql create mode 100644 SQL Scripts/SQLI-REPL-002.sql create mode 100644 SQL Scripts/SQLI-REQ-001.sql create mode 100644 SQL Scripts/SQLI-SEC-001.sql create mode 100644 SQL Scripts/SQLI-SEC-002.sql create mode 100644 SQL Scripts/SQLI-STAT-001.sql create mode 100644 SQL Scripts/SQLI-STAT-002.sql create mode 100644 SQL Scripts/SQLI-SVC-001.sql create mode 100644 SQL Scripts/SQLI-TEMP-001.sql create mode 100644 SQL Scripts/SQLI-TEMP-002.sql create mode 100644 SQL Scripts/SQLI-TEMP-003.sql create mode 100644 SQL Scripts/SQLI-TRAN-001.sql create mode 100644 SQL Scripts/SQLI-TRAN-002.sql create mode 100644 SQL Scripts/SQLI-VER-001.sql create mode 100644 SQL Scripts/SQLI-VER-002.sql create mode 100644 SQL Scripts/SQLI-WAIT-001.sql create mode 100644 SQL Scripts/SQLI-WAIT-002.sql create mode 100644 SQL Scripts/SQLI-WAIT-003.sql create mode 100644 SQL Scripts/SQLI-WAIT-004.sql create mode 100644 SQL Scripts/SQLI-WAIT-005.sql create mode 100644 SQL Scripts/SQLI-WAIT-006.sql create mode 100644 SQL Scripts/SQLI-WAIT-007.sql create mode 100644 SQL Scripts/_collectors/ActivityCollector.sql create mode 100644 SQL Scripts/_collectors/AgentJobCollector.sql create mode 100644 SQL Scripts/_collectors/AvailabilityGroupCollector.sql create mode 100644 SQL Scripts/_collectors/BlockingCollector.sql create mode 100644 SQL Scripts/_collectors/ConfigurationCollector.sql create mode 100644 SQL Scripts/_collectors/CpuCollector.sql create mode 100644 SQL Scripts/_collectors/CustomQueryCollector.sql create mode 100644 SQL Scripts/_collectors/DatabaseCollector.sql create mode 100644 SQL Scripts/_collectors/DeadlockCollector.sql create mode 100644 SQL Scripts/_collectors/ErrorLogCollector.sql create mode 100644 SQL Scripts/_collectors/FileIoCollector.sql create mode 100644 SQL Scripts/_collectors/IndexCollector.sql create mode 100644 SQL Scripts/_collectors/InstanceConfigurationCollector.sql create mode 100644 SQL Scripts/_collectors/InstanceProbe.sql create mode 100644 SQL Scripts/_collectors/LongOperationCollector.sql create mode 100644 SQL Scripts/_collectors/PerformanceCounterCollector.sql create mode 100644 SQL Scripts/_collectors/PlanAnalysisCollector.sql create mode 100644 SQL Scripts/_collectors/PlanCacheCollector.sql create mode 100644 SQL Scripts/_collectors/PrivilegeCollector.sql create mode 100644 SQL Scripts/_collectors/QueryStatsCollector.sql create mode 100644 SQL Scripts/_collectors/QueryStoreCollector.sql create mode 100644 SQL Scripts/_collectors/ReplicationCollector.sql create mode 100644 SQL Scripts/_collectors/ServerCollector.sql create mode 100644 SQL Scripts/_collectors/SuspectPageCollector.sql create mode 100644 SQL Scripts/_collectors/TempdbCollector.sql create mode 100644 SQL Scripts/_collectors/TraceFlagCollector.sql create mode 100644 SQL Scripts/_collectors/WaitSamplingCollector.sql create mode 100644 SQL Scripts/_collectors/WaitStatsCollector.sql create mode 100644 tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs create mode 100644 tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5112e6..eb41707 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,7 @@ Warnings behandles som fejl, og .NET-analyzers kører på niveauet `latest-recom | `src/SQLInsight.Contracts` | Wire-kontrakt mellem agent og Hub | | `src/SQLInsight.Hub` | Centralt API til findings, PostgreSQL (Fase 3) | | `db/permissions` | Least-privilege-script til agentens login | +| `SQL Scripts` | Ét script pr. regel med præcis den SQL, SQLInsight kører, til gennemgang af en DBA (se [SQL Scripts/README.md](SQL%20Scripts/README.md)) | | `tests/` | Unit og integrationstests | ## Sådan skriver du en regel @@ -37,16 +38,18 @@ Warnings behandles som fejl, og .NET-analyzers kører på niveauet `latest-recom 3. Et finding skal have **evidens** (de konkrete tal), **forklaring** (hvorfor det er et problem) og **anbefaling** (hvad man gør). Fix-scripts vises for brugeren og køres aldrig automatisk. 4. Skriv unit tests for alle udfald: fundet, ikke fundet, grænseværdier og versioner, hvor reglen ikke kan måles. 5. Registrér reglen i `BuiltInRules`. +6. Tilføj `SQL Scripts/.sql` med den danske header (hvad, vurdering, grænser, alvor, rettigheder, versioner, collector) og collectorens SQL, ordret og med en `-- Kilde:`-linje pr. variant. Brug en eksisterende fil som skabelon. `SqlScriptTests` fejler, hvis filen mangler, eller hvis SQL'en ikke længere er den, koden sender. ### Uden C#: community-regler -Mange regler kan skrives som en SQL-forespørgsel i en JSON-fil under `rules/community/`. De kræver ingen C# og kan afprøves direkte mod en instans med `SQLInsight.Agent rules test`. Se [docs/regler.md](docs/regler.md). +Mange regler kan skrives som en SQL-forespørgsel i en JSON-fil under `rules/community/`. De kræver ingen C# og kan afprøves direkte mod en instans med `SQLInsight.Agent rules test`. Se [docs/regler.md](docs/regler.md). Også en community-regel skal have sin `SQL Scripts/.sql` med forespørgslen ordret (se fx `SQL Scripts/COM-SEC-001.sql`). ## Queries mod overvågede servere - De skal være **read-only** og kunne køre med rettighederne fra `db/permissions/grant-least-privilege.sql`. Kræver de mere, skal scriptet og `LeastPrivilegeTests` opdateres i samme ændring. - De skal være billige: ingen `DETAILED`-scans, grænser på antal rækker og intet, der kan tage låse på brugerdata. - Tjek, hvilke versioner DMV'en og kolonnerne findes i, og angiv det som capabilities. +- Læg SQL'en i en `internal const` eller en `internal static Build…Sql`-metode i collectoren, ikke inline i `CollectAsync`, og registrér den i `tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs`. Opdatér `SQL Scripts/_collectors/.sql` og regelfilerne, der bruger collectoren. `SqlScriptTests` fejler ellers. ## Commits diff --git a/SQL Scripts/COM-AX-001.sql b/SQL Scripts/COM-AX-001.sql new file mode 100644 index 0000000..f6a34eb --- /dev/null +++ b/SQL Scripts/COM-AX-001.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-AX-001: Dynamics AX 3.0 er uden support fra Microsoft +-- =================================================================================================================== +-- Regel-id: COM-AX-001 +-- Titel: Dynamics AX 3.0 er uden support fra Microsoft +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Dynamics AX 3.0 er uden support fra Microsoft. Microsoft Dynamics AX 3.0 er for længst ude af Microsofts support, +-- også den udvidede. Der kommer ingen sikkerhedsopdateringer eller rettelser til AX-kernen, klienten eller AOS, og +-- nyere Windows- og SQL Server-versioner er aldrig testet med AX 3.0. AX 3.0 bruger desuden 32-bit RecId (int), som +-- kan løbe tør på store installationer. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{database} +-- er en Dynamics AX 3.0-database, og AX 3.0 er uden support" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-001, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-001 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version] WHERE EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 +GO diff --git a/SQL Scripts/COM-AX-002.sql b/SQL Scripts/COM-AX-002.sql new file mode 100644 index 0000000..cdec105 --- /dev/null +++ b/SQL Scripts/COM-AX-002.sql @@ -0,0 +1,75 @@ +-- =================================================================================================================== +-- COM-AX-002: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0 +-- =================================================================================================================== +-- Regel-id: COM-AX-002 +-- Titel: Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0 +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0. AX 3.0 blev kun frigivet +-- til SQL Server 2000 og 2005. På nyere versioner kan den virke, men optimizeren, låsning og udfasede funktioner +-- opfører sig anderledes end det, AX-kernen blev bygget til, og hverken Microsoft eller en AX-partner kan give +-- support. Fejl viser sig ofte først under belastning, fx ved månedsafslutning. Databasens compatibility level er +-- {compatibility_level}. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "AX +-- 3.0-databasen {database} ligger på SQL Server {sql_version}" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-002, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-002 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version], d.compatibility_level AS [compatibility_level] FROM sys.databases d WHERE d.database_id = DB_ID() AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) > 9 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') AND COLUMNPROPERTY((SELECT TOP 1 object_id FROM sys.tables WHERE name = N'SQLDICTIONARY'), N'RECID', 'Precision') = 10 +GO diff --git a/SQL Scripts/COM-AX-003.sql b/SQL Scripts/COM-AX-003.sql new file mode 100644 index 0000000..0221f5a --- /dev/null +++ b/SQL Scripts/COM-AX-003.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-AX-003: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014) +-- =================================================================================================================== +-- Regel-id: COM-AX-003 +-- Titel: Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014) +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014). Dynamics AX sender mange små, +-- hyppige forespørgsler og cursorer fra AOS. Når de får parallelle planer, bruger de flere tråde end nødvendigt, +-- venter på hinanden (CXPACKET) og holder låse længere, så brugerne oplever langsomme formularer og blokering. +-- Microsofts vejledning for Dynamics AX er derfor max degree of parallelism = 1 på instansen. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "MAXDOP er +-- {maxdop} på instansen med Dynamics AX-databasen {database}" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-003, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-003 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT CAST(c.value_in_use AS int) AS [maxdop] FROM sys.configurations c WHERE c.name = 'max degree of parallelism' AND CAST(c.value_in_use AS int) <> 1 AND CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int) < 13 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +GO diff --git a/SQL Scripts/COM-AX-004.sql b/SQL Scripts/COM-AX-004.sql new file mode 100644 index 0000000..e44631c --- /dev/null +++ b/SQL Scripts/COM-AX-004.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-AX-004: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere) +-- =================================================================================================================== +-- Regel-id: COM-AX-004 +-- Titel: Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere) +-- Alvor: Medium +-- Versioner: SQL Server 2016 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere). Dynamics AX sender mange små, hyppige +-- forespørgsler og cursorer fra AOS. Når de får parallelle planer, bruger de flere tråde end nødvendigt, venter på +-- hinanden (CXPACKET) og holder låse længere. Microsofts vejledning for Dynamics AX er MAXDOP 1. Instansen har +-- {server_maxdop}, og databasen har {database_maxdop} (0 = instansens værdi gælder). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Dynamics +-- AX-databasen {database} kører med MAXDOP større end 1" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-004, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-004 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2016 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT CAST(c.value_in_use AS int) AS [server_maxdop], CAST(s.value AS int) AS [database_maxdop] FROM sys.configurations c CROSS JOIN sys.database_scoped_configurations s WHERE c.name = 'max degree of parallelism' AND s.name = 'MAXDOP' AND (CASE WHEN CAST(s.value AS int) = 0 THEN CAST(c.value_in_use AS int) ELSE CAST(s.value AS int) END) <> 1 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +GO diff --git a/SQL Scripts/COM-AX-005.sql b/SQL Scripts/COM-AX-005.sql new file mode 100644 index 0000000..821be8c --- /dev/null +++ b/SQL Scripts/COM-AX-005.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-AX-005: Dynamics AX-tabeller uden unikt RecId-indeks +-- =================================================================================================================== +-- Regel-id: COM-AX-005 +-- Titel: Dynamics AX-tabeller uden unikt RecId-indeks +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Dynamics AX-tabeller uden unikt RecId-indeks. AX identificerer hver post med RecId og opretter normalt et unikt +-- indeks på den (I_RECID) ved synkronisering. Mangler det, er en synkronisering typisk fejlet eller +-- afbrudt, eller indekset er fjernet manuelt. Uden det bliver opslag på RecId (som AX laver hele tiden, fx ved +-- opdatering og sletning af én post) til scanninger, og dubletter opdages ikke. Eksempler: {examples}. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables} +-- AX-tabeller i {database} har intet unikt indeks på RECID" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-005, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-005 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT COUNT(*) AS [tables], LEFT(STUFF((SELECT TOP 10 N', ' + t2.name FROM sys.tables t2 WHERE COLUMNPROPERTY(t2.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t2.object_id AND i.is_unique = 1 AND c.name = N'RECID') ORDER BY t2.name FOR XML PATH('')), 1, 2, ''), 400) AS [examples] FROM sys.tables t WHERE COLUMNPROPERTY(t.object_id, N'RECID', 'ColumnId') IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys.indexes i JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id WHERE i.object_id = t.object_id AND i.is_unique = 1 AND c.name = N'RECID') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') HAVING COUNT(*) > 0 +GO diff --git a/SQL Scripts/COM-AX-006.sql b/SQL Scripts/COM-AX-006.sql new file mode 100644 index 0000000..3f78a38 --- /dev/null +++ b/SQL Scripts/COM-AX-006.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-AX-006: Store Dynamics AX-tabeller uden clustered index (heaps) +-- =================================================================================================================== +-- Regel-id: COM-AX-006 +-- Titel: Store Dynamics AX-tabeller uden clustered index (heaps) +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Store Dynamics AX-tabeller uden clustered index (heaps). En tabel uden clustered index (en heap) får spredte data +-- og 'forwarded records', når poster opdateres, og den kan ikke defragmenteres med en indeks-rebuild. I AX styres +-- det af tabellens ClusterIndex-egenskab i AOT, som i AX 3.0 ofte ikke er sat på standard- og kundetabeller. På +-- store tabeller giver det langsomme opslag og mere I/O. I alt {rows} rækker. Største: {largest}. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{tables} +-- store AX-tabeller i {database} har intet clustered index" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-006, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-006 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT COUNT(*) AS [tables], SUM(h.row_count) AS [rows], LEFT(STUFF((SELECT TOP 10 N', ' + OBJECT_NAME(h2.object_id) + N' (' + CAST(h2.row_count AS nvarchar(20)) + N')' FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h2 WHERE h2.row_count >= 100000 ORDER BY h2.row_count DESC FOR XML PATH('')), 1, 2, ''), 400) AS [largest] FROM (SELECT p.object_id, SUM(p.rows) AS row_count FROM sys.partitions p JOIN sys.tables t ON t.object_id = p.object_id WHERE p.index_id = 0 GROUP BY p.object_id) h WHERE h.row_count >= 100000 AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') HAVING COUNT(*) > 0 +GO diff --git a/SQL Scripts/COM-AX-007.sql b/SQL Scripts/COM-AX-007.sql new file mode 100644 index 0000000..88c0b80 --- /dev/null +++ b/SQL Scripts/COM-AX-007.sql @@ -0,0 +1,75 @@ +-- =================================================================================================================== +-- COM-AX-007: Automatiske statistikker er slået fra i en Dynamics AX-database +-- =================================================================================================================== +-- Regel-id: COM-AX-007 +-- Titel: Automatiske statistikker er slået fra i en Dynamics AX-database +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Automatiske statistikker er slået fra i en Dynamics AX-database. AX bygger sine forespørgsler dynamisk ud fra +-- brugernes filtre, så optimizeren er helt afhængig af, at statistikkerne er der og er opdaterede. Uden automatiske +-- statistikker vælger SQL Server ofte forkerte planer, især efter store kørsler som lageropgørelse og nummerserier, +-- og formularer og batchjob kan pludselig blive meget langsomme. Auto create statistics: {auto_create_statistics}. +-- Auto update statistics: {auto_update_statistics}. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Automatiske +-- statistikker er slået fra i AX-databasen {database}" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/dynamics-ax.json (id COM-AX-007, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-AX-007 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT CASE WHEN d.is_auto_create_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_create_statistics], CASE WHEN d.is_auto_update_stats_on = 1 THEN 'til' ELSE 'fra' END AS [auto_update_statistics] FROM sys.databases d WHERE d.database_id = DB_ID() AND (d.is_auto_create_stats_on = 0 OR d.is_auto_update_stats_on = 0) AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SQLDICTIONARY') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'DATAAREA') AND EXISTS (SELECT 1 FROM sys.tables WHERE name = N'SYSCONFIG') +GO diff --git a/SQL Scripts/COM-CFG-001.sql b/SQL Scripts/COM-CFG-001.sql new file mode 100644 index 0000000..1760f1f --- /dev/null +++ b/SQL Scripts/COM-CFG-001.sql @@ -0,0 +1,62 @@ +-- =================================================================================================================== +-- COM-CFG-001: Default trace er slået fra +-- =================================================================================================================== +-- Regel-id: COM-CFG-001 +-- Titel: Default trace er slået fra +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Default trace er slået fra. Default trace er en let sporing, der registrerer bl.a. autogrowth, ændringer af +-- objekter og logins. Uden den er det svært at efterforske, hvad der skete, efter en hændelse. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen +-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Default trace er slået fra" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/security.json (id COM-CFG-001, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-CFG-001 (server-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +USE [master]; +SELECT CAST(value_in_use AS int) AS [value_in_use] FROM sys.configurations WHERE name = 'default trace enabled' AND value_in_use = 0 +GO diff --git a/SQL Scripts/COM-DB-001.sql b/SQL Scripts/COM-DB-001.sql new file mode 100644 index 0000000..1ced6a4 --- /dev/null +++ b/SQL Scripts/COM-DB-001.sql @@ -0,0 +1,73 @@ +-- =================================================================================================================== +-- COM-DB-001: Fremmednøgler, som SQL Server ikke stoler på +-- =================================================================================================================== +-- Regel-id: COM-DB-001 +-- Titel: Fremmednøgler, som SQL Server ikke stoler på +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fremmednøgler, som SQL Server ikke stoler på. Nøglen er blevet slået til igen uden WITH CHECK, fx efter en +-- dataindlæsning. Den håndhæves for nye rækker, men SQL Server kan ikke stole på eksisterende data, så optimizeren +-- udnytter den ikke til at fjerne joins, og der kan ligge ugyldige data. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "{database}: +-- fremmednøglen {constraint} på {table} er ikke betroet" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/integrity.json (id COM-DB-001, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-DB-001 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT s.name + '.' + o.name AS [table], fk.name AS [constraint] FROM sys.foreign_keys fk JOIN sys.objects o ON o.object_id = fk.parent_object_id JOIN sys.schemas s ON s.schema_id = o.schema_id WHERE fk.is_not_trusted = 1 AND fk.is_disabled = 0 +GO diff --git a/SQL Scripts/COM-FT-001.sql b/SQL Scripts/COM-FT-001.sql new file mode 100644 index 0000000..e1aa5ca --- /dev/null +++ b/SQL Scripts/COM-FT-001.sql @@ -0,0 +1,73 @@ +-- =================================================================================================================== +-- COM-FT-001: Fulltext-katalog er sat på pause, fordi disken er fuld +-- =================================================================================================================== +-- Regel-id: COM-FT-001 +-- Titel: Fulltext-katalog er sat på pause, fordi disken er fuld +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fulltext-katalog er sat på pause, fordi disken er fuld. SQL Server har sat opbygningen af fulltext-kataloget +-- {catalog} på pause, fordi der ikke er plads på disken. Søgninger med CONTAINS og FREETEXT bruger det indeks, der +-- var bygget, så nye og ændrede rækker findes ikke. Det fortsætter ikke af sig selv, før der er plads. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Fulltext-kataloget {catalog} i {database} er stoppet: disken er fuld" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/fulltext.json (id COM-FT-001, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-FT-001 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT c.name AS [catalog], FULLTEXTCATALOGPROPERTY(c.name, 'ItemCount') AS [items] FROM sys.fulltext_catalogs c WHERE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') = 8 +GO diff --git a/SQL Scripts/COM-FT-002.sql b/SQL Scripts/COM-FT-002.sql new file mode 100644 index 0000000..e819ae5 --- /dev/null +++ b/SQL Scripts/COM-FT-002.sql @@ -0,0 +1,73 @@ +-- =================================================================================================================== +-- COM-FT-002: Fulltext-katalog er sat på pause eller drosles +-- =================================================================================================================== +-- Regel-id: COM-FT-002 +-- Titel: Fulltext-katalog er sat på pause eller drosles +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fulltext-katalog er sat på pause eller drosles. Opbygningen af fulltext-kataloget {catalog} er {status}. Mens den +-- står stille, finder fulltext-søgninger ikke nye og ændrede rækker. 'Droslet' betyder, at SQL Server har bremset +-- opbygningen på grund af ressourcemangel (hukommelse eller CPU); 'pause' kan være sat manuelt eller af SQL Server. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Fulltext-kataloget {catalog} i {database} er {status}" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/fulltext.json (id COM-FT-002, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-FT-002 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT c.name AS [catalog], CASE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') WHEN 2 THEN 'sat på pause' ELSE 'droslet' END AS [status] FROM sys.fulltext_catalogs c WHERE FULLTEXTCATALOGPROPERTY(c.name, 'PopulateStatus') IN (2, 3) +GO diff --git a/SQL Scripts/COM-FT-003.sql b/SQL Scripts/COM-FT-003.sql new file mode 100644 index 0000000..c21af61 --- /dev/null +++ b/SQL Scripts/COM-FT-003.sql @@ -0,0 +1,73 @@ +-- =================================================================================================================== +-- COM-FT-003: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge +-- =================================================================================================================== +-- Regel-id: COM-FT-003 +-- Titel: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge. Fulltext-indekset på {table} +-- opdateres ikke automatisk (change tracking er {change_tracking}), og kataloget {catalog} blev sidst bygget +-- {last_population}. Søgninger finder ikke rækker, der er oprettet eller ændret siden. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Fulltext-indekset på {table} i {database} er sidst opdateret {last_population}" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/fulltext.json (id COM-FT-003, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-FT-003 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog], CASE i.change_tracking_state WHEN 'M' THEN 'manuel' ELSE 'slået fra' END AS [change_tracking], CONVERT(varchar(16), DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101'), 120) AS [last_population] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.change_tracking_state IN ('M', 'O') AND i.is_enabled = 1 AND FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge') > 0 AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') < DATEADD(day, -7, GETDATE()) AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') >= DATEADD(day, -30, GETDATE()) +GO diff --git a/SQL Scripts/COM-FT-004.sql b/SQL Scripts/COM-FT-004.sql new file mode 100644 index 0000000..8559ad1 --- /dev/null +++ b/SQL Scripts/COM-FT-004.sql @@ -0,0 +1,74 @@ +-- =================================================================================================================== +-- COM-FT-004: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage +-- =================================================================================================================== +-- Regel-id: COM-FT-004 +-- Titel: Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage. Fulltext-indekset på {table} +-- opdateres ikke automatisk (change tracking er {change_tracking}), og kataloget {catalog} er ikke bygget i over 30 +-- dage (sidst {last_population}). Søgninger med CONTAINS og FREETEXT giver derfor forældede resultater. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Fulltext-indekset på {table} i {database} er ikke opdateret siden {last_population}" ({kolonne} udfyldes fra +-- rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/fulltext.json (id COM-FT-004, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-FT-004 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog], CASE i.change_tracking_state WHEN 'M' THEN 'manuel' ELSE 'slået fra' END AS [change_tracking], CONVERT(varchar(16), DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101'), 120) AS [last_population] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.change_tracking_state IN ('M', 'O') AND i.is_enabled = 1 AND FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge') > 0 AND DATEADD(second, FULLTEXTCATALOGPROPERTY(c.name, 'PopulateCompletionAge'), '19900101') < DATEADD(day, -30, GETDATE()) +GO diff --git a/SQL Scripts/COM-FT-005.sql b/SQL Scripts/COM-FT-005.sql new file mode 100644 index 0000000..4214028 --- /dev/null +++ b/SQL Scripts/COM-FT-005.sql @@ -0,0 +1,72 @@ +-- =================================================================================================================== +-- COM-FT-005: Fulltext-indeks er deaktiveret +-- =================================================================================================================== +-- Regel-id: COM-FT-005 +-- Titel: Fulltext-indeks er deaktiveret +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Fulltext-indeks er deaktiveret. Fulltext-indekset på {table} (kataloget {catalog}) er slået fra. Det opdateres +-- ikke, og fulltext-søgninger mod tabellen fejler eller finder intet, afhængigt af hvordan applikationen søger. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres i hver online brugerdatabase, agenten kan åbne (CustomQueryCollector.DatabasesSql), +-- og kolonnen database tilføjes hver række. Kører instansen en ældre version end minimumVersion, er reglen "ikke +-- relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Fulltext-indekset på {table} i {database} er deaktiveret" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/fulltext.json (id COM-FT-005, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-FT-005 (database-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Databaserne, reglen køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +-- Agenten kører dette i hver brugerdatabase fra listen ovenfor. Ret [msdb] til den database, du vil tjekke. +USE [msdb]; +SELECT OBJECT_NAME(i.object_id) AS [table], c.name AS [catalog] FROM sys.fulltext_indexes i JOIN sys.fulltext_catalogs c ON c.fulltext_catalog_id = i.fulltext_catalog_id WHERE i.is_enabled = 0 +GO diff --git a/SQL Scripts/COM-SEC-001.sql b/SQL Scripts/COM-SEC-001.sql new file mode 100644 index 0000000..b165f43 --- /dev/null +++ b/SQL Scripts/COM-SEC-001.sql @@ -0,0 +1,62 @@ +-- =================================================================================================================== +-- COM-SEC-001: Login'et sa er aktivt +-- =================================================================================================================== +-- Regel-id: COM-SEC-001 +-- Titel: Login'et sa er aktivt +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Login'et sa er aktivt. sa er det login, angribere prøver først, fordi navnet er kendt, og det har fulde +-- rettigheder. Password-politik håndhæves: {password_policy}. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen +-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: "Det +-- indbyggede login '{login}' er aktivt" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/security.json (id COM-SEC-001, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-SEC-001 (server-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +USE [master]; +SELECT name AS [login], CASE WHEN is_policy_checked = 1 THEN 'ja' ELSE 'nej' END AS [password_policy] FROM sys.sql_logins WHERE principal_id = 1 AND is_disabled = 0 +GO diff --git a/SQL Scripts/COM-SEC-002.sql b/SQL Scripts/COM-SEC-002.sql new file mode 100644 index 0000000..a7790c7 --- /dev/null +++ b/SQL Scripts/COM-SEC-002.sql @@ -0,0 +1,63 @@ +-- =================================================================================================================== +-- COM-SEC-002: Database ejes af et login, der ikke findes +-- =================================================================================================================== +-- Regel-id: COM-SEC-002 +-- Titel: Database ejes af et login, der ikke findes +-- Alvor: Low +-- Versioner: SQL Server 2008 og nyere. Kræver ingen særlige capabilities (DeclarativeRule). +-- +-- Hvad tjekkes, og hvorfor: +-- Database ejes af et login, der ikke findes. Ejeren er typisk en medarbejder, som er stoppet, eller en database, +-- der er gendannet fra en anden server. Det giver fejl i fx EXECUTE AS OWNER, cross-database ownership chaining og +-- nogle jobs. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ingen C#-logik ud over at læse resultatet: hver række, forespørgslen returnerer, er et fund (højst 100 rækker pr. +-- regel). Forespørgslen køres én gang i master. Kører instansen en ældre version end minimumVersion, er reglen +-- "ikke relevant". Fejler forespørgslen pga. manglende rettigheder, er reglen "ikke målbar". Fundets titel: +-- "Databasen {database} ejes af et login, der ikke længere findes" ({kolonne} udfyldes fra rækken). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen konfigurationsnøgle: eventuelle grænser står direkte i forespørgslen (fx antal rækker eller dage). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CustomQueryCollector +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Medlemmer: Db.CurrentDatabaseSql, CustomQueryCollector.DatabasesSql, +-- CustomQueryCollector.BuildRuleSql(database, query) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Delt script: SQL Scripts/_collectors/CustomQueryCollector.sql +-- Regelkode: src/SQLInsight.Rules/Custom/DeclarativeRule.cs : DeclarativeRule.Evaluate; reglen selv: +-- rules/community/security.json (id COM-SEC-002, indlejret i regelpakken) +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CustomQueryCollector: Community-regel COM-SEC-002 (server-regel) +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +USE [master]; +SELECT d.name AS [database], CONVERT(varchar(100), d.owner_sid, 1) AS [owner_sid] FROM sys.databases d LEFT JOIN sys.server_principals p ON p.sid = d.owner_sid WHERE p.sid IS NULL AND d.database_id > 4 +GO diff --git a/SQL Scripts/README.md b/SQL Scripts/README.md new file mode 100644 index 0000000..170ccc0 --- /dev/null +++ b/SQL Scripts/README.md @@ -0,0 +1,230 @@ +# SQL Scripts + +Denne mappe indeholder **præcis den SQL, SQLInsight Pro kører mod en overvåget SQL Server**, ordnet efter regel. Den er +lavet, så en DBA kan gennemgå hver eneste sætning, før agenten får adgang til en server: hvad der læses, med hvilke +rettigheder, på hvilke versioner, og hvordan reglen bagefter vurderer data. + +Agenten kører **ikke** filerne her. De er en læsbar kopi af SQL'en i koden, og unit tests sikrer, at kopien er +identisk med koden (se [Hold mappen i sync](#hold-mappen-i-sync)). + +## Navngivning + +| Fil | Indhold | +|---|---| +| `.sql` | Én fil pr. regel, navngivet med regel-id'et præcis som i systemet: `SQLI-…` for de indbyggede regler (C#), `COM-…` for community-regler (JSON i `rules/community/`). | +| `_collectors/.sql` | Alle forespørgsler fra én collector. Flere regler deler ofte samme collector; regelfilerne gentager SQL'en, så hver fil kan køres for sig. | +| `_collectors/InstanceProbe.sql` | Registreringen af instansen (version, udgave, Always On) og de sessionsindstillinger, agenten sætter på hver forbindelse. | + +## Opbygning af en regelfil + +Hver fil starter med en kommentar-header på dansk: + +- **Regel-id, titel, alvor (severity)** og **understøttede versioner**. +- **Hvad tjekkes, og hvorfor.** +- **Vurdering**: de fleste regler afgør ikke selv noget i SQL. Collectoren læser data, og reglen vurderer dem i C# + (fx "fund hvis ventetiden er over 5 ms i gennemsnit mellem to målinger"). Logikken er beskrevet i almindeligt dansk. +- **Grænseværdier** med konfigurationsnøgle (i agentens `appsettings.json`, sektionen `SQLInsight`) og standardværdi, eller + "fast i koden". +- **Rettigheder** som præcise `GRANT`-sætninger (`[agent]` = agentens login), svarende til + `db/permissions/grant-least-privilege.sql`. +- **Data kommer fra**: collectoren, kildefilen og konstanten/metoden, der indeholder SQL'en, samt intervallet. +- **Regelkode**: filen og klassen, der vurderer data. +- **Read-only**: hvad scriptet gør ud over SELECT. + +Derefter følger SQL'en, én blok pr. variant: + +```sql +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT ... +GO +``` + +- `Variant` siger, hvilke versioner (eller hvilken situation) teksten sendes for. Agenten vælger varianten ud fra + instansens version og capabilities. +- `Kilde` er nøglen, som testen bruger til at sammenligne med koden. +- `Kode` er stedet i koden, hvor SQL'en står eller bygges. + +## Sådan køres scriptene + +1. Åbn filen i SQL Server Management Studio (eller kør `sqlcmd -i "SQL Scripts\SQLI-DB-007.sql"`). +2. Forbind **som agentens login** (fx gMSA'en), eller et login med præcis de rettigheder, `grant-least-privilege.sql` + giver. Som sysadmin virker alt, men så ser man ikke, om en rettighed mangler. +3. Kør hele filen. Første batch sætter de samme sessionsindstillinger som agenten: + `SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;` +4. Varianter for andre SQL Server-versioner end din kan fejle (fx SQL Server 2000-varianten mod `master.dbo.sysaltfiles` + eller kolonner, der først findes i nyere versioner). Det er forventet; SSMS fortsætter med næste batch. + +Bemærk: + +- **Read-only.** Alle scripts læser kun: SELECT mod katalog-views og DMV'er, `DBCC TRACESTATUS`, `DBCC SQLPERF(WAITSTATS)` + (kun SQL Server 2000), `xp_readerrorlog`, `xp_msver`, `sys.fn_trace_gettable`, lokale variabler og `USE`. + **Én undtagelse:** `SQLI-DLK-001` / `_collectors/DeadlockCollector.sql` (SQL Server 2012+) opretter en midlertidig + tabel `#system_health` i tempdb og sletter den igen i samme batch. Den er privat for sessionen, og ingen brugerdata, + indstillinger eller permanente objekter ændres, men det er strengt taget en skrivning i tempdb. Det står også i + filernes header. +- **Per-database-forespørgsler** (indexes, statistikker, Query Store, database-scoped community-regler) kører agenten i + hver online brugerdatabase. Her er de vist for `msdb`, som findes på alle instanser, så scriptet kan køres uændret. + Ret `[msdb]` / `N'msdb'` til den database, du vil se. +- **Værdier, agenten beregner ved kørsel** (default trace-filens sti, tidspunktet for forrige måling) er erstattet af + variabler, der erklæres lige over forespørgslen med realistiske standardværdier. Kommentaren over viser, hvad agenten + indsætter. Grænser fra konfigurationen (fx 1024 MB log, 15 minutter) er vist med standardværdien. +- **Ikke med her:** fix-scripts i fundene (de vises for brugeren og køres aldrig automatisk), agentens eget repository + (fx baseline-historikken bag `SQLI-ANOM-001`, som ikke hentes fra den overvågede server) og kundens lokale regler + (`LOCAL-…`). + +## Hold mappen i sync + +`tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs` fejler, hvis mappen og koden ikke stemmer overens: + +- `Every_registered_rule_has_a_script_and_every_script_a_rule`: hver indbygget regel (`BuiltInRules`) og hver + community-regel i regelpakken har en `.sql`, og der er ingen filer for ukendte id'er. +- `Scripts_contain_the_current_sql_from_the_code`: hver blok med `-- Kilde: ` indeholder SQL'en, som koden + bygger den i dag, ordret (nøglerne står i `SqlScriptSources.cs`). Kun værdier, agenten beregner ved kørsel, må stå + anderledes. +- `Every_collector_query_is_registered_and_in_a_collector_script`: hver SQL-konstant og hver `Build…Sql`-metode i + collectorerne er registreret, og hver registreret forespørgsel står i et `_collectors/`-script. +- `Community_rule_scripts_contain_the_rule_query_verbatim`: community-reglens `query` fra JSON står ordret i filen. +- `Scripts_are_read_only_apart_from_the_documented_temp_table`: ingen skrivende sætninger ud over undtagelsen ovenfor. + +En ny regel skal derfor have sin `SQL Scripts/.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger +testen på de filer, der skal opdateres. + +## Regler + +98 regler: 82 indbyggede (C#) og 16 community-regler (JSON). + +| Regel-id | Fil | Titel | Collector(s) | Alvor | +|---|---|---|---|---| +| SQLI-AG-001 | [SQLI-AG-001.sql](SQLI-AG-001.sql) | Always On availability group er ikke sund | AvailabilityGroupCollector | High for DISCONNECTED, NOT_HEALTHY og suspenderet; Medium for PARTIALLY_HEALTHY. | +| SQLI-AG-002 | [SQLI-AG-002.sql](SQLI-AG-002.sql) | Sekundær replika er langt bagud | AvailabilityGroupCollector | Medium | +| SQLI-AG-003 | [SQLI-AG-003.sql](SQLI-AG-003.sql) | En node eller et vidne i clusteret er nede | AvailabilityGroupCollector | High | +| SQLI-ANOM-001 | [SQLI-ANOM-001.sql](SQLI-ANOM-001.sql) | Usædvanlig belastning i forhold til det normale | CpuCollector, PerformanceCounterCollector | CPU: Medium. Batch requests: Low ved stigning, Medium ved fald. Page life expectancy: Medium. | +| SQLI-BLK-001 | [SQLI-BLK-001.sql](SQLI-BLK-001.sql) | Langvarig blocking | BlockingCollector | Medium; High når længste ventetid er mindst 5 minutter eller mindst 10 sessioner er blokeret. | +| SQLI-CFG-001 | [SQLI-CFG-001.sql](SQLI-CFG-001.sql) | max server memory er ikke sat | ConfigurationCollector, ServerCollector | High | +| SQLI-CFG-002 | [SQLI-CFG-002.sql](SQLI-CFG-002.sql) | max server memory efterlader for lidt til Windows | ConfigurationCollector, ServerCollector | Medium | +| SQLI-CFG-003 | [SQLI-CFG-003.sql](SQLI-CFG-003.sql) | cost threshold for parallelism står på standardværdien 5 | ConfigurationCollector, ServerCollector | Medium | +| SQLI-CFG-004 | [SQLI-CFG-004.sql](SQLI-CFG-004.sql) | MAXDOP passer ikke til serverens NUMA-layout | ConfigurationCollector, ServerCollector | Medium | +| SQLI-CFG-005 | [SQLI-CFG-005.sql](SQLI-CFG-005.sql) | Plan cache er fyldt med engangsplaner | ConfigurationCollector, PlanCacheCollector | Low | +| SQLI-CFG-006 | [SQLI-CFG-006.sql](SQLI-CFG-006.sql) | Priority boost er slået til | ConfigurationCollector | High | +| SQLI-CFG-007 | [SQLI-CFG-007.sql](SQLI-CFG-007.sql) | Lightweight pooling (fiber mode) er slået til | ConfigurationCollector | High | +| SQLI-CFG-008 | [SQLI-CFG-008.sql](SQLI-CFG-008.sql) | xp_cmdshell er slået til | ConfigurationCollector | Medium | +| SQLI-CFG-009 | [SQLI-CFG-009.sql](SQLI-CFG-009.sql) | Backup-komprimering er ikke standard | ConfigurationCollector, InstanceProbe | Low | +| SQLI-CFG-010 | [SQLI-CFG-010.sql](SQLI-CFG-010.sql) | Dedicated Admin Connection kan kun bruges lokalt | ConfigurationCollector | Low | +| SQLI-CFG-011 | [SQLI-CFG-011.sql](SQLI-CFG-011.sql) | Konfigurationsændringer venter på RECONFIGURE eller genstart | ConfigurationCollector | Low | +| SQLI-CFG-012 | [SQLI-CFG-012.sql](SQLI-CFG-012.sql) | Deadlocks bliver ikke registreret | TraceFlagCollector, InstanceProbe | Low | +| SQLI-CFG-013 | [SQLI-CFG-013.sql](SQLI-CFG-013.sql) | Konfiguration ændret | InstanceConfigurationCollector | Info | +| SQLI-CPU-001 | [SQLI-CPU-001.sql](SQLI-CPU-001.sql) | Vedvarende højt CPU-forbrug | CpuCollector | SQL Server: Medium fra 75 %, High fra 90 %. Andre processer: Medium. | +| SQLI-CPU-002 | [SQLI-CPU-002.sql](SQLI-CPU-002.sql) | Sætning kører næsten uafbrudt | QueryStatsCollector | Medium | +| SQLI-DB-001 | [SQLI-DB-001.sql](SQLI-DB-001.sql) | Database er ikke tilgængelig | DatabaseCollector | Critical for SUSPECT, RECOVERY_PENDING og EMERGENCY; Low for OFFLINE. | +| SQLI-DB-002 | [SQLI-DB-002.sql](SQLI-DB-002.sql) | AUTO_SHRINK er slået til | DatabaseCollector | High | +| SQLI-DB-003 | [SQLI-DB-003.sql](SQLI-DB-003.sql) | AUTO_CLOSE er slået til | DatabaseCollector | Medium | +| SQLI-DB-004 | [SQLI-DB-004.sql](SQLI-DB-004.sql) | Sidekontrol er ikke CHECKSUM | DatabaseCollector | Medium | +| SQLI-DB-005 | [SQLI-DB-005.sql](SQLI-DB-005.sql) | TRUSTWORTHY er slået til | DatabaseCollector | Medium | +| SQLI-DB-006 | [SQLI-DB-006.sql](SQLI-DB-006.sql) | Database kører med et ældre compatibility level | DatabaseCollector, InstanceProbe | Low | +| SQLI-DB-007 | [SQLI-DB-007.sql](SQLI-DB-007.sql) | Database mangler en nylig fuld backup | DatabaseCollector | Critical hvis aldrig backet op; High hvis ældre end 7 dage; Medium hvis databasen er i en availability group (backup kan være taget på en anden replika). | +| SQLI-DB-008 | [SQLI-DB-008.sql](SQLI-DB-008.sql) | Database i FULL recovery uden log-backups | DatabaseCollector | High; Medium hvis databasen er i en availability group. | +| SQLI-DB-009 | [SQLI-DB-009.sql](SQLI-DB-009.sql) | DBCC CHECKDB er ikke kørt for nylig | DatabaseCollector | High hvis aldrig kørt eller ældre end 30 dage; ellers Medium. | +| SQLI-DB-010 | [SQLI-DB-010.sql](SQLI-DB-010.sql) | Korrupte sider registreret i suspect_pages | SuspectPageCollector | Critical | +| SQLI-DB-011 | [SQLI-DB-011.sql](SQLI-DB-011.sql) | Transaktionsloggen har for mange VLF'er | DatabaseCollector | Medium; High over 10.000 VLF'er. | +| SQLI-DB-012 | [SQLI-DB-012.sql](SQLI-DB-012.sql) | Uhensigtsmæssig autogrowth | DatabaseCollector | Low | +| SQLI-DB-013 | [SQLI-DB-013.sql](SQLI-DB-013.sql) | Databasefil nærmer sig sin maksimale størrelse | DatabaseCollector | High fra 90 %; Critical når filen har nået MAXSIZE. | +| SQLI-DB-014 | [SQLI-DB-014.sql](SQLI-DB-014.sql) | Query Store indsamler ikke længere data | DatabaseCollector | High ved ERROR; ellers Medium. | +| SQLI-DB-015 | [SQLI-DB-015.sql](SQLI-DB-015.sql) | tempdb har for få eller ulige store datafiler | DatabaseCollector, ServerCollector | Medium for for få filer; Low for ulige store filer. | +| SQLI-DLK-001 | [SQLI-DLK-001.sql](SQLI-DLK-001.sql) | Deadlocks | DeadlockCollector | Medium; High fra 10 deadlocks siden sidste måling. | +| SQLI-IDX-001 | [SQLI-IDX-001.sql](SQLI-IDX-001.sql) | Manglende index med stor effekt | IndexCollector | Medium hvis forbedringsmålet er mindst 1.000.000; ellers Low. | +| SQLI-IDX-002 | [SQLI-IDX-002.sql](SQLI-IDX-002.sql) | Ubrugt index koster ved hver skrivning | IndexCollector | Low; Medium fra 1 GB. | +| SQLI-IDX-003 | [SQLI-IDX-003.sql](SQLI-IDX-003.sql) | Duplikeret eller overlappende index | IndexCollector | Medium for nøjagtige dubletter; Low for overlappende. | +| SQLI-IDX-004 | [SQLI-IDX-004.sql](SQLI-IDX-004.sql) | Heap med mange forwarded records | IndexCollector | Medium | +| SQLI-IO-001 | [SQLI-IO-001.sql](SQLI-IO-001.sql) | Høj læselatens på drev med datafiler | FileIoCollector | Medium; High fra 50 ms. | +| SQLI-IO-002 | [SQLI-IO-002.sql](SQLI-IO-002.sql) | Høj skrivelatens på drev med logfiler | FileIoCollector | Medium; High fra 15 ms. | +| SQLI-JOB-001 | [SQLI-JOB-001.sql](SQLI-JOB-001.sql) | SQL Agent-job fejler | AgentJobCollector | High hvis jobnavnet indeholder backup, checkdb, integrity eller dbcc; ellers Medium. | +| SQLI-JOB-002 | [SQLI-JOB-002.sql](SQLI-JOB-002.sql) | SQL Server Agent kører ikke | AgentJobCollector, InstanceProbe | High når der er aktive jobs; ellers Low. | +| SQLI-LOG-001 | [SQLI-LOG-001.sql](SQLI-LOG-001.sql) | SQL Server har læst beskadigede sider (fejl 823/824/825) | ErrorLogCollector | Critical for 823/824; High for 825. | +| SQLI-LOG-002 | [SQLI-LOG-002.sql](SQLI-LOG-002.sql) | I/O-forespørgsler, der tager over 15 sekunder (fejl 833) | ErrorLogCollector | High | +| SQLI-LOG-003 | [SQLI-LOG-003.sql](SQLI-LOG-003.sql) | Alvorlige fejl i SQL Servers fejllog (severity 17 og derover) | ErrorLogCollector | High fra severity 20; ellers Medium. | +| SQLI-LOG-004 | [SQLI-LOG-004.sql](SQLI-LOG-004.sql) | Mange mislykkede logins | ErrorLogCollector | Medium | +| SQLI-LOG-005 | [SQLI-LOG-005.sql](SQLI-LOG-005.sql) | SQL Server har lavet et stack dump | ErrorLogCollector | High | +| SQLI-LOG-006 | [SQLI-LOG-006.sql](SQLI-LOG-006.sql) | Fejl i Always On availability groups | ErrorLogCollector | High | +| SQLI-MEM-001 | [SQLI-MEM-001.sql](SQLI-MEM-001.sql) | Forespørgsler venter på hukommelse | PerformanceCounterCollector, WaitStatsCollector | High | +| SQLI-OPS-001 | [SQLI-OPS-001.sql](SQLI-OPS-001.sql) | Backup eller restore kører markant længere end normalt | LongOperationCollector | Medium | +| SQLI-PLAN-001 | [SQLI-PLAN-001.sql](SQLI-PLAN-001.sql) | Implicit konvertering forhindrer index seek | PlanAnalysisCollector | Medium | +| SQLI-PLAN-002 | [SQLI-PLAN-002.sql](SQLI-PLAN-002.sql) | Join uden join-betingelse (kartesisk produkt) | PlanAnalysisCollector | High | +| SQLI-PLAN-003 | [SQLI-PLAN-003.sql](SQLI-PLAN-003.sql) | Skalar funktion kaldes for hver række | PlanAnalysisCollector | Medium | +| SQLI-PLAN-004 | [SQLI-PLAN-004.sql](SQLI-PLAN-004.sql) | Sorteringer og hash joins spilder til tempdb | PlanAnalysisCollector | Medium | +| SQLI-PLAN-005 | [SQLI-PLAN-005.sql](SQLI-PLAN-005.sql) | Forespørgsel får langt mere hukommelse, end den bruger | PlanAnalysisCollector | Medium | +| SQLI-QS-001 | [SQLI-QS-001.sql](SQLI-QS-001.sql) | Planregression: forespørgsel har fået en langsommere plan | QueryStoreCollector | Medium; High når ekstra tid er mindst 10 minutter i døgnet. | +| SQLI-QS-002 | [SQLI-QS-002.sql](SQLI-QS-002.sql) | Forceret plan kan ikke bruges | QueryStoreCollector | High | +| SQLI-QS-003 | [SQLI-QS-003.sql](SQLI-QS-003.sql) | Parameterfølsom forespørgsel (parameter sniffing) | QueryStoreCollector | Medium | +| SQLI-REPL-001 | [SQLI-REPL-001.sql](SQLI-REPL-001.sql) | Replikering fejler | ReplicationCollector | High | +| SQLI-REPL-002 | [SQLI-REPL-002.sql](SQLI-REPL-002.sql) | Replikering er forsinket | ReplicationCollector | Medium | +| SQLI-REQ-001 | [SQLI-REQ-001.sql](SQLI-REQ-001.sql) | Sætning har kørt længe | ActivityCollector | Medium | +| SQLI-SEC-001 | [SQLI-SEC-001.sql](SQLI-SEC-001.sql) | Ny forhøjet rettighed på serveren | PrivilegeCollector | High for sysadmin, securityadmin og CONTROL SERVER; Medium for de øvrige faste serverroller. | +| SQLI-SEC-002 | [SQLI-SEC-002.sql](SQLI-SEC-002.sql) | For mange personer og konti med sysadmin | PrivilegeCollector | Medium | +| SQLI-STAT-001 | [SQLI-STAT-001.sql](SQLI-STAT-001.sql) | Forældede statistikker på store tabeller | IndexCollector | Low | +| SQLI-STAT-002 | [SQLI-STAT-002.sql](SQLI-STAT-002.sql) | Automatisk statistik er slået fra | DatabaseCollector | Medium | +| SQLI-SVC-001 | [SQLI-SVC-001.sql](SQLI-SVC-001.sql) | En SQL Server-tjeneste, der skal starte automatisk, kører ikke | InstanceConfigurationCollector | High | +| SQLI-TEMP-001 | [SQLI-TEMP-001.sql](SQLI-TEMP-001.sql) | Version store i tempdb er stor eller vokser | TempdbCollector | Medium | +| SQLI-TEMP-002 | [SQLI-TEMP-002.sql](SQLI-TEMP-002.sql) | tempdb er næsten fuld | TempdbCollector | High | +| SQLI-TEMP-003 | [SQLI-TEMP-003.sql](SQLI-TEMP-003.sql) | Session bruger meget tempdb i lang tid | TempdbCollector | Medium | +| SQLI-TRAN-001 | [SQLI-TRAN-001.sql](SQLI-TRAN-001.sql) | Transaktion har været åben længe | ActivityCollector | Medium; High når den har været åben 6 x grænsen. | +| SQLI-TRAN-002 | [SQLI-TRAN-002.sql](SQLI-TRAN-002.sql) | Transaktion bruger meget transaktionslog | ActivityCollector | Medium | +| SQLI-VER-001 | [SQLI-VER-001.sql](SQLI-VER-001.sql) | SQL Server-versionens supportstatus | InstanceProbe | Critical når extended support er udløbet; High når mainstream er udløbet og der er højst 365 dage til extended support slutter; ellers Medium i extended support. | +| SQLI-VER-002 | [SQLI-VER-002.sql](SQLI-VER-002.sql) | Instansen mangler opdateringer | InstanceProbe | High når versionen stadig er i support; Medium når den er ude af support (så er SQLI-VER-001 det vigtigste fund). | +| SQLI-WAIT-001 | [SQLI-WAIT-001.sql](SQLI-WAIT-001.sql) | SQL Server løber tør for worker threads | WaitStatsCollector | Critical | +| SQLI-WAIT-002 | [SQLI-WAIT-002.sql](SQLI-WAIT-002.sql) | Tasks venter på CPU (høj signal wait) | WaitStatsCollector | Medium; High fra 40 %. | +| SQLI-WAIT-003 | [SQLI-WAIT-003.sql](SQLI-WAIT-003.sql) | Sessioner venter meget på låse | WaitStatsCollector | Medium; High fra 5 ventende tasks i gennemsnit. | +| SQLI-WAIT-004 | [SQLI-WAIT-004.sql](SQLI-WAIT-004.sql) | Forespørgsler venter meget på at læse data fra disk | WaitStatsCollector | Medium; High fra 8 ventende tasks i gennemsnit. | +| SQLI-WAIT-005 | [SQLI-WAIT-005.sql](SQLI-WAIT-005.sql) | Commits venter på langsom skrivning til transaktionsloggen | WaitStatsCollector | Medium; High fra 15 ms i gennemsnit. | +| SQLI-WAIT-006 | [SQLI-WAIT-006.sql](SQLI-WAIT-006.sql) | Parallelle forespørgsler venter meget på hinanden | WaitStatsCollector | Medium; High fra 16 ventende tasks i gennemsnit. | +| SQLI-WAIT-007 | [SQLI-WAIT-007.sql](SQLI-WAIT-007.sql) | Contention på sider i hukommelsen (PAGELATCH) | WaitStatsCollector | Medium; High fra 4 ventende tasks i gennemsnit. | +| COM-AX-001 | [COM-AX-001.sql](COM-AX-001.sql) | Dynamics AX 3.0 er uden support fra Microsoft | CustomQueryCollector | Medium | +| COM-AX-002 | [COM-AX-002.sql](COM-AX-002.sql) | Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0 | CustomQueryCollector | Medium | +| COM-AX-003 | [COM-AX-003.sql](COM-AX-003.sql) | Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014) | CustomQueryCollector | Medium | +| COM-AX-004 | [COM-AX-004.sql](COM-AX-004.sql) | Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere) | CustomQueryCollector | Medium | +| COM-AX-005 | [COM-AX-005.sql](COM-AX-005.sql) | Dynamics AX-tabeller uden unikt RecId-indeks | CustomQueryCollector | Medium | +| COM-AX-006 | [COM-AX-006.sql](COM-AX-006.sql) | Store Dynamics AX-tabeller uden clustered index (heaps) | CustomQueryCollector | Medium | +| COM-AX-007 | [COM-AX-007.sql](COM-AX-007.sql) | Automatiske statistikker er slået fra i en Dynamics AX-database | CustomQueryCollector | High | +| COM-CFG-001 | [COM-CFG-001.sql](COM-CFG-001.sql) | Default trace er slået fra | CustomQueryCollector | Low | +| COM-DB-001 | [COM-DB-001.sql](COM-DB-001.sql) | Fremmednøgler, som SQL Server ikke stoler på | CustomQueryCollector | Low | +| COM-FT-001 | [COM-FT-001.sql](COM-FT-001.sql) | Fulltext-katalog er sat på pause, fordi disken er fuld | CustomQueryCollector | High | +| COM-FT-002 | [COM-FT-002.sql](COM-FT-002.sql) | Fulltext-katalog er sat på pause eller drosles | CustomQueryCollector | Medium | +| COM-FT-003 | [COM-FT-003.sql](COM-FT-003.sql) | Fulltext-indeks uden automatisk opdatering er ikke opdateret i over en uge | CustomQueryCollector | Low | +| COM-FT-004 | [COM-FT-004.sql](COM-FT-004.sql) | Fulltext-indeks uden automatisk opdatering er ikke opdateret i over 30 dage | CustomQueryCollector | Medium | +| COM-FT-005 | [COM-FT-005.sql](COM-FT-005.sql) | Fulltext-indeks er deaktiveret | CustomQueryCollector | Medium | +| COM-SEC-001 | [COM-SEC-001.sql](COM-SEC-001.sql) | Login'et sa er aktivt | CustomQueryCollector | Medium | +| COM-SEC-002 | [COM-SEC-002.sql](COM-SEC-002.sql) | Database ejes af et login, der ikke findes | CustomQueryCollector | Low | + +## Collectors + +| Collector | Fil | Interval | Versioner | Bruges af | +|---|---|---|---|---| +| [InstanceProbe](_collectors/InstanceProbe.sql) | `src/SQLInsight.Connectivity/InstanceProbe.cs` | én gang, når agenten forbinder til instansen | Alle (SQL Server 2000 og nyere) | 6 regler | +| [ServerCollector](_collectors/ServerCollector.sql) | `src/SQLInsight.Collectors/Instance/ServerCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:server) | Alle (SQL Server 2000 og nyere) | 5 regler | +| [ConfigurationCollector](_collectors/ConfigurationCollector.sql) | `src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:configuration) | Alle (SQL Server 2000 og nyere) | 11 regler | +| [TraceFlagCollector](_collectors/TraceFlagCollector.sql) | `src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:trace-flags) | Alle (SQL Server 2000 og nyere) | 1 regel | +| [InstanceConfigurationCollector](_collectors/InstanceConfigurationCollector.sql) | `src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:instance-config) | Alle; tjenester og IFI fra SQL Server 2008 R2 SP1 | 2 regler | +| [PrivilegeCollector](_collectors/PrivilegeCollector.sql) | `src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:privileges) | SQL Server 2005 og nyere | 2 regler | +| [DatabaseCollector](_collectors/DatabaseCollector.sql) | `src/SQLInsight.Collectors/Databases/DatabaseCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:databases) | Alle (SQL Server 2000 og nyere); CHECKDB og VLF'er fra 2016 SP2; Query Store fra 2016 | 15 regler | +| [SuspectPageCollector](_collectors/SuspectPageCollector.sql) | `src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:suspect-pages) | SQL Server 2005 og nyere | 1 regel | +| [PlanCacheCollector](_collectors/PlanCacheCollector.sql) | `src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:plan-cache) | SQL Server 2005 og nyere | 1 regel | +| [WaitStatsCollector](_collectors/WaitStatsCollector.sql) | `src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) | Alle (SQL Server 2000 og nyere) | 8 regler | +| [FileIoCollector](_collectors/FileIoCollector.sql) | `src/SQLInsight.Collectors/Performance/FileIoCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:file-io) | SQL Server 2005 og nyere; drev fra 2008 R2 SP1 | 2 regler | +| [QueryStatsCollector](_collectors/QueryStatsCollector.sql) | `src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:query-stats) | SQL Server 2005 og nyere | 1 regel | +| [WaitSamplingCollector](_collectors/WaitSamplingCollector.sql) | `src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs` | 15 sek som standard (SQLInsight:CollectorIntervals:wait-sampling) | SQL Server 2005 og nyere | ingen regel | +| [LongOperationCollector](_collectors/LongOperationCollector.sql) | `src/SQLInsight.Collectors/Instance/LongOperationCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:long-operations) | SQL Server 2005 og nyere | 1 regel | +| [ErrorLogCollector](_collectors/ErrorLogCollector.sql) | `src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:error-log) | SQL Server 2005 og nyere | 6 regler | +| [TempdbCollector](_collectors/TempdbCollector.sql) | `src/SQLInsight.Collectors/Instance/TempdbCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:tempdb) | SQL Server 2005 og nyere | 3 regler | +| [ReplicationCollector](_collectors/ReplicationCollector.sql) | `src/SQLInsight.Collectors/Instance/ReplicationCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:replication) | SQL Server 2005 og nyere | 2 regler | +| [PerformanceCounterCollector](_collectors/PerformanceCounterCollector.sql) | `src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) | Alle (SQL Server 2000 og nyere) | 2 regler | +| [CpuCollector](_collectors/CpuCollector.sql) | `src/SQLInsight.Collectors/Performance/CpuCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:cpu) | SQL Server 2005 og nyere | 2 regler | +| [BlockingCollector](_collectors/BlockingCollector.sql) | `src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:blocking) | Alle (SQL Server 2000 og nyere) | 1 regel | +| [ActivityCollector](_collectors/ActivityCollector.sql) | `src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:activity) | SQL Server 2005 og nyere | 3 regler | +| [DeadlockCollector](_collectors/DeadlockCollector.sql) | `src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:deadlocks) | SQL Server 2008 og nyere (filer fra 2012, ellers ring buffer) | 1 regel | +| [QueryStoreCollector](_collectors/QueryStoreCollector.sql) | `src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs` | 30 min som standard (SQLInsight:CollectorIntervals:query-store) | SQL Server 2016 og nyere | 3 regler | +| [PlanAnalysisCollector](_collectors/PlanAnalysisCollector.sql) | `src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs` | 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) | SQL Server 2005 SP2 og nyere | 5 regler | +| [IndexCollector](_collectors/IndexCollector.sql) | `src/SQLInsight.Collectors/Databases/IndexCollector.cs` | 6 timer som standard (SQLInsight:CollectorIntervals:indexes) | SQL Server 2005 og nyere | 5 regler | +| [AvailabilityGroupCollector](_collectors/AvailabilityGroupCollector.sql) | `src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs` | 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) | SQL Server 2012 og nyere med Always On slået til | 3 regler | +| [AgentJobCollector](_collectors/AgentJobCollector.sql) | `src/SQLInsight.Collectors/Instance/AgentJobCollector.cs` | 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) | SQL Server 2005 og nyere | 2 regler | +| [CustomQueryCollector](_collectors/CustomQueryCollector.sql) | `src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs` | 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) | SQL Server 2005 og nyere (pr. regel: minimumVersion) | 16 regler | diff --git a/SQL Scripts/SQLI-AG-001.sql b/SQL Scripts/SQLI-AG-001.sql new file mode 100644 index 0000000..ac3decf --- /dev/null +++ b/SQL Scripts/SQLI-AG-001.sql @@ -0,0 +1,85 @@ +-- =================================================================================================================== +-- SQLI-AG-001: Always On availability group er ikke sund +-- =================================================================================================================== +-- Regel-id: SQLI-AG-001 +-- Titel: Always On availability group er ikke sund +-- Alvor: High for DISCONNECTED, NOT_HEALTHY og suspenderet; Medium for PARTIALLY_HEALTHY. +-- Versioner: SQL Server 2012 og nyere med Always On slået til (IsHadrEnabled). +-- +-- Hvad tjekkes, og hvorfor: +-- Om replikaer eller databaser i en availability group ikke er sunde: afbrudt, ikke synkroniseret eller +-- suspenderet. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund pr. replika med connected_state_desc = DISCONNECTED eller synchronization_health_desc +-- NOT_HEALTHY/PARTIALLY_HEALTHY, og pr. database-replika, der er suspenderet eller har synchronization_health_desc +-- NOT_HEALTHY/PARTIALLY_HEALTHY. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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 +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AvailabilityGroupCollector +-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs +-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql, +-- AvailabilityGroupCollector.ClusterMembersSql +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) +-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AvailabilityGroupHealthRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og +-- Windows-clusterets medlemmer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.ReplicasSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql +SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc, + ar.availability_mode_desc, ar.failover_mode_desc +FROM sys.availability_groups ag +JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql +SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local, + CASE WHEN ars.role = 1 THEN 1 ELSE 0 END, + drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc, + drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time +FROM sys.dm_hadr_database_replica_states drs +JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id +JOIN sys.availability_groups ag ON ag.group_id = drs.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster) +-- Kilde: AvailabilityGroupCollector.ClusterMembersSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql +SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members +GO diff --git a/SQL Scripts/SQLI-AG-002.sql b/SQL Scripts/SQLI-AG-002.sql new file mode 100644 index 0000000..6f3d672 --- /dev/null +++ b/SQL Scripts/SQLI-AG-002.sql @@ -0,0 +1,84 @@ +-- =================================================================================================================== +-- SQLI-AG-002: Sekundær replika er langt bagud +-- =================================================================================================================== +-- Regel-id: SQLI-AG-002 +-- Titel: Sekundær replika er langt bagud +-- Alvor: Medium +-- Versioner: SQL Server 2012 og nyere med Always On slået til. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en sekundær replika er langt bagud: muligt datatab (commit-forsinkelse) eller lang failover-tid (redo queue). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver sekundær, ikke suspenderet database-replika: muligt datatab = last_commit_time på den primære - på den +-- sekundære (sekunder); recovery-tid = redo_queue_size / redo_rate (sekunder). Fund hvis datatab >= 60 s eller +-- recovery-tid >= 600 s. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 60 sekunder (MaxDataLossSeconds) og 600 sekunder (MaxRecoverySeconds), 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+ +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AvailabilityGroupCollector +-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs +-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql, +-- AvailabilityGroupCollector.ClusterMembersSql +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) +-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AvailabilityGroupLagRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og +-- Windows-clusterets medlemmer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.ReplicasSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql +SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc, + ar.availability_mode_desc, ar.failover_mode_desc +FROM sys.availability_groups ag +JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql +SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local, + CASE WHEN ars.role = 1 THEN 1 ELSE 0 END, + drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc, + drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time +FROM sys.dm_hadr_database_replica_states drs +JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id +JOIN sys.availability_groups ag ON ag.group_id = drs.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster) +-- Kilde: AvailabilityGroupCollector.ClusterMembersSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql +SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members +GO diff --git a/SQL Scripts/SQLI-AG-003.sql b/SQL Scripts/SQLI-AG-003.sql new file mode 100644 index 0000000..317dac9 --- /dev/null +++ b/SQL Scripts/SQLI-AG-003.sql @@ -0,0 +1,83 @@ +-- =================================================================================================================== +-- SQLI-AG-003: En node eller et vidne i clusteret er nede +-- =================================================================================================================== +-- Regel-id: SQLI-AG-003 +-- Titel: En node eller et vidne i clusteret er nede +-- Alvor: High +-- Versioner: SQL Server 2012 og nyere med Always On og et Windows Server Failover Cluster (tom ved CLUSTER_TYPE +-- NONE/EXTERNAL). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en node eller et vidne i Windows-clusteret under availability groups er nede, så et tab mere kan tage quorum. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. medlem af sys.dm_hadr_cluster_members med member_state <> 1 (UP). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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 +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AvailabilityGroupCollector +-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs +-- Medlemmer: AvailabilityGroupCollector.ReplicasSql, AvailabilityGroupCollector.DatabasesSql, +-- AvailabilityGroupCollector.ClusterMembersSql +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) +-- Delt script: SQL Scripts/_collectors/AvailabilityGroupCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : ClusterMemberDownRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- AvailabilityGroupCollector: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og +-- Windows-clusterets medlemmer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.ReplicasSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql +SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc, + ar.availability_mode_desc, ar.failover_mode_desc +FROM sys.availability_groups ag +JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql +SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local, + CASE WHEN ars.role = 1 THEN 1 ELSE 0 END, + drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc, + drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time +FROM sys.dm_hadr_database_replica_states drs +JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id +JOIN sys.availability_groups ag ON ag.group_id = drs.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster) +-- Kilde: AvailabilityGroupCollector.ClusterMembersSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql +SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members +GO diff --git a/SQL Scripts/SQLI-ANOM-001.sql b/SQL Scripts/SQLI-ANOM-001.sql new file mode 100644 index 0000000..da37522 --- /dev/null +++ b/SQL Scripts/SQLI-ANOM-001.sql @@ -0,0 +1,130 @@ +-- =================================================================================================================== +-- SQLI-ANOM-001: Usædvanlig belastning i forhold til det normale +-- =================================================================================================================== +-- Regel-id: SQLI-ANOM-001 +-- Titel: Usædvanlig belastning i forhold til det normale +-- Alvor: CPU: Medium. Batch requests: Low ved stigning, Medium ved fald. Page life expectancy: Medium. +-- Versioner: SQL Server 2005 og nyere (CPU); tællerne fra 2000. +-- +-- Hvad tjekkes, og hvorfor: +-- Om belastningen er usædvanlig i forhold til, hvad der er normalt for netop denne time på ugen. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Baseline beregnes af agenten i C# fra dens EGET repository (timegennemsnit de seneste 28 dage for samme time på +-- ugen +/- 1 time, mindst 6 målepunkter) - ikke fra den overvågede server. CPU: gns. af de seneste 15 minutters +-- ProcessUtilization >= 50 % og >= middel + max(3 x std.afv., 20). Batch requests/s (forskel mellem to +-- tællermålinger): >= 100 og >= middel + max(3 x std.afv., middel) giver stigning; middel >= 50 og nu <= 30 % af +-- middel giver fald. Page life expectancy: middel >= 300 s og nu <= 30 % af middel. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Fast i koden (AnomalyRule og BaselineCalculator.MinSamples = 6). +-- +-- 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- CpuCollector +-- Fil: src/SQLInsight.Collectors/Performance/CpuCollector.cs +-- Medlemmer: CpuCollector.Sql, CpuCollector.HostPlatformSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:cpu) +-- Delt script: SQL Scripts/_collectors/CpuCollector.sql +-- PerformanceCounterCollector +-- Fil: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs +-- Medlemmer: Db.StartTimeSql(version), PerformanceCounterCollector.BuildSql(dmv: true), +-- PerformanceCounterCollector.BuildSql(dmv: false) +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) +-- Delt script: SQL Scripts/_collectors/PerformanceCounterCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/AnomalyRule.cs : AnomalyRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CpuCollector: SQL Servers og systemets CPU-forbrug pr. minut (scheduler monitor ring buffer, op til 60 minutter). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CpuCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.Sql +DECLARE @now_ticks bigint; +SELECT @now_ticks = ms_ticks FROM sys.dm_os_sys_info; +SELECT TOP (60) + DATEADD(second, CAST((rb.[timestamp] - @now_ticks) / 1000 AS int), GETUTCDATE()), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int'), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') +FROM ( + SELECT [timestamp], CONVERT(xml, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%%' +) rb +ORDER BY rb.[timestamp] DESC +GO + +-- Variant: SQL Server 2017 og nyere (Windows eller Linux) +-- Kilde: CpuCollector.HostPlatformSql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.HostPlatformSql +SELECT host_platform FROM sys.dm_os_host_info +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- PerformanceCounterCollector: Udvalgte performance counters: memory grants, page life expectancy, forbindelser, +-- blokerede processer, batch requests og kompileringer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: PerformanceCounterCollector.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: PerformanceCounterCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: PerformanceCounterCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PerformanceCounterCollector.BuildSql(dmv) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: true) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM sys.dm_os_performance_counters +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: PerformanceCounterCollector.BuildSql(sysperfinfo) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: false) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM master.dbo.sysperfinfo +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO diff --git a/SQL Scripts/SQLI-BLK-001.sql b/SQL Scripts/SQLI-BLK-001.sql new file mode 100644 index 0000000..36271fe --- /dev/null +++ b/SQL Scripts/SQLI-BLK-001.sql @@ -0,0 +1,102 @@ +-- =================================================================================================================== +-- SQLI-BLK-001: Langvarig blocking +-- =================================================================================================================== +-- Regel-id: SQLI-BLK-001 +-- Titel: Langvarig blocking +-- Alvor: Medium; High når længste ventetid er mindst 5 minutter eller mindst 10 sessioner er blokeret. +-- Versioner: Alle (SQL Server 2000 via master.dbo.sysprocesses). +-- +-- Hvad tjekkes, og hvorfor: +-- Om der er blocking-kæder, der har varet længe nok til at genere brugerne. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- SQL'en henter alle blokerede sessioner og deres blokerere hvert minut. I C# findes head blockers (blokerer andre, +-- er ikke selv blokeret); for hver følges hele kæden. Fund hvis den længst ventende i kæden har ventet mindst 30 +-- sekunder. En head blocker, der er 'sleeping' med åben transaktion, beskrives særskilt. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 30.000 ms (BlockingRule.MinBlockedMs); High fra 300.000 ms eller 10 blokerede (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- BlockingCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs +-- Medlemmer: BlockingCollector.BuildSql2005(version), BlockingCollector.Sql2000 +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking) +-- Delt script: SQL Scripts/_collectors/BlockingCollector.sql +-- Regelkode: src/SQLInsight.Rules/Concurrency/ConcurrencyRules.cs : BlockingRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning og åbne transaktioner. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: BlockingCollector.BuildSql2005(2012+) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(COALESCE(r.database_id, s.database_id)), s.login_name, s.host_name, s.program_name, s.open_transaction_count, + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: BlockingCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: BlockingCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000 +SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype), + p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name), + p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch +FROM master.dbo.sysprocesses p +WHERE (p.blocked > 0 AND p.blocked <> p.spid) + OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid) +GO diff --git a/SQL Scripts/SQLI-CFG-001.sql b/SQL Scripts/SQLI-CFG-001.sql new file mode 100644 index 0000000..3a657cf --- /dev/null +++ b/SQL Scripts/SQLI-CFG-001.sql @@ -0,0 +1,145 @@ +-- =================================================================================================================== +-- SQLI-CFG-001: max server memory er ikke sat +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-001 +-- Titel: max server memory er ikke sat +-- Alvor: High +-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om 'max server memory (MB)' står på standardværdien, så SQL Server kan tage al hukommelse og presse Windows til +-- paging. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'max server memory (MB)' (ConfigurationCollector) er >= 2147483647. Den anbefalede +-- værdi i fundet beregnes af fysisk hukommelse (ServerCollector): max(fysisk / 2, fysisk - max(4096 MB, 10 % af +-- fysisk)). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Standardværdien 2147483647 (int.MaxValue). Reserve til Windows: det største af 4096 MB og 10 % +-- (MemoryGuidance.Reserve, 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- ServerCollector +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Delt script: SQL Scripts/_collectors/ServerCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxServerMemoryDefaultRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/SQLI-CFG-002.sql b/SQL Scripts/SQLI-CFG-002.sql new file mode 100644 index 0000000..e1715fc --- /dev/null +++ b/SQL Scripts/SQLI-CFG-002.sql @@ -0,0 +1,145 @@ +-- =================================================================================================================== +-- SQLI-CFG-002: max server memory efterlader for lidt til Windows +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-002 +-- Titel: max server memory efterlader for lidt til Windows +-- Alvor: Medium +-- Versioner: Alle, hvor fysisk hukommelse kan aflæses (SQL Server 2005+ via sys.dm_os_sys_info, 2000 via xp_msver). +-- Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om 'max server memory (MB)' er sat så højt, at der er for lidt tilbage til Windows, tråd-stacks og allokeringer +-- uden for buffer pool. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis max server memory < 2147483647 (standardværdien er SQLI-CFG-001) og fysisk hukommelse - max server +-- memory < halvdelen af reserven, hvor reserven er max(4096 MB, 10 % af fysisk). Fundet anbefaler max(fysisk / 2, +-- fysisk - reserven). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Reserve: max(4096 MB, 10 %) (MemoryGuidance.Reserve, fast i koden); fund under halvdelen af reserven. +-- +-- 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- ServerCollector +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Delt script: SQL Scripts/_collectors/ServerCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxServerMemoryTooHighRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/SQLI-CFG-003.sql b/SQL Scripts/SQLI-CFG-003.sql new file mode 100644 index 0000000..51d9991 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-003.sql @@ -0,0 +1,142 @@ +-- =================================================================================================================== +-- SQLI-CFG-003: cost threshold for parallelism står på standardværdien 5 +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-003 +-- Titel: cost threshold for parallelism står på standardværdien 5 +-- Alvor: Medium +-- Versioner: Alle (SQL Server 2000 og nyere). +-- +-- Hvad tjekkes, og hvorfor: +-- Om 'cost threshold for parallelism' står på standardværdien 5, så selv små forespørgsler kører parallelt. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'cost threshold for parallelism' = 5, 'max degree of parallelism' <> 1 og cpu_count +-- (ServerCollector) > 1. Fix-scriptet i fundet foreslår 50 (køres aldrig automatisk). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Standardværdien 5; anbefalet startværdi 50 (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- ServerCollector +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Delt script: SQL Scripts/_collectors/ServerCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : CostThresholdRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/SQLI-CFG-004.sql b/SQL Scripts/SQLI-CFG-004.sql new file mode 100644 index 0000000..ecf360a --- /dev/null +++ b/SQL Scripts/SQLI-CFG-004.sql @@ -0,0 +1,144 @@ +-- =================================================================================================================== +-- SQLI-CFG-004: MAXDOP passer ikke til serverens NUMA-layout +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-004 +-- Titel: MAXDOP passer ikke til serverens NUMA-layout +-- Alvor: Medium +-- Versioner: Alle (NUMA-noder tælles fra SQL Server 2008; på 2005/2000 regnes med én node). +-- +-- Hvad tjekkes, og hvorfor: +-- Om MAXDOP passer til serverens NUMA-layout efter Microsofts vejledning. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- schedulers = antal VISIBLE ONLINE schedulers (ellers cpu_count); noder = antal forskellige parent_node_id (mindst +-- 1); pr. node = schedulers / noder. Anbefalet = min(8, pr. node) ved én node; ved flere noder pr. node hvis <= 16, +-- ellers min(16, pr. node / 2). Fund hvis schedulers > 1 og enten MAXDOP = 0 og anbefalet < schedulers, eller +-- MAXDOP > anbefalet. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Microsofts vejledning (MaxDopRule.Recommended, 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- ServerCollector +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Delt script: SQL Scripts/_collectors/ServerCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : MaxDopRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/SQLI-CFG-005.sql b/SQL Scripts/SQLI-CFG-005.sql new file mode 100644 index 0000000..5bb835b --- /dev/null +++ b/SQL Scripts/SQLI-CFG-005.sql @@ -0,0 +1,89 @@ +-- =================================================================================================================== +-- SQLI-CFG-005: Plan cache er fyldt med engangsplaner +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-005 +-- Titel: Plan cache er fyldt med engangsplaner +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere (DynamicManagementViews; indstillingen findes fra 2008 - mangler den, er +-- reglen "ikke målbar"). +-- +-- Hvad tjekkes, og hvorfor: +-- Om plan cache er fyldt med ad hoc-planer, der kun er brugt én gang, mens 'optimize for ad hoc workloads' er slået +-- fra. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis 'optimize for ad hoc workloads' value_in_use = 0, og engangs-ad hoc-planer (objtype = Adhoc og +-- usecounts = 1) fylder mindst 25 % af plan cache og mindst 100 MB. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Andel >= 25 % og >= 100 MB (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): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- PlanCacheCollector +-- Fil: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs +-- Medlemmer: PlanCacheCollector.Sql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:plan-cache) +-- Delt script: SQL Scripts/_collectors/PlanCacheCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : OptimizeForAdHocRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanCacheCollector: Plan cache: antal og størrelse af planer og af ad hoc-planer brugt én gang. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PlanCacheCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs : PlanCacheCollector.Sql +SELECT + COUNT_BIG(*), + ISNULL(SUM(CAST(size_in_bytes AS bigint)), 0) / 1048576.0, + ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN 1 ELSE 0 END), 0), + ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN CAST(size_in_bytes AS bigint) ELSE 0 END), 0) / 1048576.0 +FROM sys.dm_exec_cached_plans +GO diff --git a/SQL Scripts/SQLI-CFG-006.sql b/SQL Scripts/SQLI-CFG-006.sql new file mode 100644 index 0000000..291b134 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-006.sql @@ -0,0 +1,65 @@ +-- =================================================================================================================== +-- SQLI-CFG-006: Priority boost er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-006 +-- Titel: Priority boost er slået til +-- Alvor: High +-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om 'priority boost' er slået til. Det kan udsulte Windows og cluster-tjenester og er forældet. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'priority boost' <> 0. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen (alt andet end 0 er et fund). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create +-- (ConfigurationOptionRule "priority boost") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/SQLI-CFG-007.sql b/SQL Scripts/SQLI-CFG-007.sql new file mode 100644 index 0000000..809a467 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-007.sql @@ -0,0 +1,66 @@ +-- =================================================================================================================== +-- SQLI-CFG-007: Lightweight pooling (fiber mode) er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-007 +-- Titel: Lightweight pooling (fiber mode) er slået til +-- Alvor: High +-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om 'lightweight pooling' (fiber mode) er slået til. Det understøttes ikke af bl.a. CLR og distribuerede +-- transaktioner. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'lightweight pooling' <> 0. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen (alt andet end 0 er et fund). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create +-- (ConfigurationOptionRule "lightweight pooling") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/SQLI-CFG-008.sql b/SQL Scripts/SQLI-CFG-008.sql new file mode 100644 index 0000000..81511b0 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-008.sql @@ -0,0 +1,66 @@ +-- =================================================================================================================== +-- SQLI-CFG-008: xp_cmdshell er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-008 +-- Titel: xp_cmdshell er slået til +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere (på 2000 findes indstillingen ikke i sp_configure: "ikke målbar"). Ikke +-- relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om xp_cmdshell er slået til, så T-SQL kan køre vilkårlige Windows-kommandoer med tjenestekontoens rettigheder. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'xp_cmdshell' <> 0. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen (alt andet end 0 er et fund). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create +-- (ConfigurationOptionRule "xp_cmdshell") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/SQLI-CFG-009.sql b/SQL Scripts/SQLI-CFG-009.sql new file mode 100644 index 0000000..5511737 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-009.sql @@ -0,0 +1,91 @@ +-- =================================================================================================================== +-- SQLI-CFG-009: Backup-komprimering er ikke standard +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-009 +-- Titel: Backup-komprimering er ikke standard +-- Alvor: Low +-- Versioner: SQL Server 2008 og nyere (indstillingen findes ikke før: "ikke målbar"). Bestået på Express +-- (EngineEdition 4 fra InstanceProbe), som ikke kan komprimere backups. Ikke relevant på Azure SQL +-- Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om backup-komprimering er standard, så hvert backup-job ikke selv skal huske WITH COMPRESSION. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'backup compression default' = 0 og udgaven ikke er Express. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create +-- (ConfigurationOptionRule "backup compression default") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-CFG-010.sql b/SQL Scripts/SQLI-CFG-010.sql new file mode 100644 index 0000000..bf575b2 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-010.sql @@ -0,0 +1,66 @@ +-- =================================================================================================================== +-- SQLI-CFG-010: Dedicated Admin Connection kan kun bruges lokalt +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-010 +-- Titel: Dedicated Admin Connection kan kun bruges lokalt +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere (på 2000 findes indstillingen ikke: "ikke målbar"). Ikke relevant på Azure +-- SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om Dedicated Admin Connection (DAC) kun kan bruges fra selve serveren. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis value_in_use for 'remote admin connections' = 0. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : SimpleConfigurationRules.Create +-- (ConfigurationOptionRule "remote admin connections") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/SQLI-CFG-011.sql b/SQL Scripts/SQLI-CFG-011.sql new file mode 100644 index 0000000..17f9000 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-011.sql @@ -0,0 +1,66 @@ +-- =================================================================================================================== +-- SQLI-CFG-011: Konfigurationsændringer venter på RECONFIGURE eller genstart +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-011 +-- Titel: Konfigurationsændringer venter på RECONFIGURE eller genstart +-- Alvor: Low +-- Versioner: Alle (SQL Server 2000 og nyere). +-- +-- Hvad tjekkes, og hvorfor: +-- Om indstillinger er ændret med sp_configure uden RECONFIGURE eller genstart, så de træder i kraft på et uventet +-- tidspunkt. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund med alle indstillinger, hvor value (konfigureret) <> value_in_use (i brug). 'min server memory (MB)' +-- ignoreres (konfigureret 0, mens SQL Server rapporterer det effektive minimum). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Medlemmer: ConfigurationCollector.Sql2005, ConfigurationCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Delt script: SQL Scripts/_collectors/ConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : PendingConfigurationRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ConfigurationCollector: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/SQLI-CFG-012.sql b/SQL Scripts/SQLI-CFG-012.sql new file mode 100644 index 0000000..67004a7 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-012.sql @@ -0,0 +1,81 @@ +-- =================================================================================================================== +-- SQLI-CFG-012: Deadlocks bliver ikke registreret +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-012 +-- Titel: Deadlocks bliver ikke registreret +-- Alvor: Low +-- Versioner: Har kun betydning på SQL Server 2000 og 2005: fra 2008 (system_health-sessionen, udledt af versionen +-- fra InstanceProbe) er reglen altid bestået. +-- +-- Hvad tjekkes, og hvorfor: +-- Om deadlocks kan analyseres bagefter på versioner uden system_health. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Bestået hvis instansen har system_health (2008+). Ellers fund, hvis hverken trace flag 1222 eller 1204 er slået +-- til globalt (DBCC TRACESTATUS(-1): Status = 1 og Global = 1). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- TraceFlagCollector +-- Fil: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs +-- Medlemmer: TraceFlagCollector.Sql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:trace-flags) +-- Delt script: SQL Scripts/_collectors/TraceFlagCollector.sql +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : DeadlockTraceFlagRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- TraceFlagCollector: De globalt slåede trace flags. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: TraceFlagCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql +DBCC TRACESTATUS(-1) WITH NO_INFOMSGS +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-CFG-013.sql b/SQL Scripts/SQLI-CFG-013.sql new file mode 100644 index 0000000..39ff753 --- /dev/null +++ b/SQL Scripts/SQLI-CFG-013.sql @@ -0,0 +1,121 @@ +-- =================================================================================================================== +-- SQLI-CFG-013: Konfiguration ændret +-- =================================================================================================================== +-- Regel-id: SQLI-CFG-013 +-- Titel: Konfiguration ændret +-- Alvor: Info +-- Versioner: Alle. Tjenestekonti kun fra SQL Server 2008 R2 SP1 (sys.dm_server_services). +-- +-- Hvad tjekkes, og hvorfor: +-- Om nogen har ændret en sp_configure-indstilling, et globalt trace flag eller en tjenestekonto. Information, så +-- ændringen er synlig. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- InstanceConfigurationCollector bygger et "fingeraftryk" af sp_configure (value_in_use), globale trace flags, +-- tjenester og konti, version, udgave, collation, tempdb-filer m.m. og sammenligner med forrige måling (gemt i +-- agentens repository, så en ændring mens agenten var nede også ses). Ændringer i områderne sp_configure, trace +-- flag og tjenestekonto inden for de seneste 24 timer giver ét Info-fund pr. ændring. Alt sker i C#; SQL'en læser +-- kun de nuværende værdier. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Fundet holdes åbent i 24 timer (ConfigurationFingerprint.RecentWindow, 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+ +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- InstanceConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs +-- Medlemmer: InstanceConfigurationCollector.PropertiesSql, InstanceConfigurationCollector.TempdbSql, +-- InstanceConfigurationCollector.ColumnsSql, InstanceConfigurationCollector.BuildServicesSql(true), +-- InstanceConfigurationCollector.BuildServicesSql(false), InstanceConfigurationCollector.MemoryModelSql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:instance-config) +-- Delt script: SQL Scripts/_collectors/InstanceConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Configuration/ConfigurationRules.cs : ConfigurationChangedRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceConfigurationCollector: Instansens opsætning til konfigurationssiden og ændringsloggen: egenskaber, +-- tempdb-filer, tjenester og konti, Instant File Initialization, memory model, sp_configure og trace flags. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- Variant: Alle versioner (SQL Server 2000 og nyere) (samme forespørgsel som TraceFlagCollector) +-- Kilde: TraceFlagCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql +DBCC TRACESTATUS(-1) WITH NO_INFOMSGS +GO + +-- Variant: Alle versioner +-- Kilde: InstanceConfigurationCollector.PropertiesSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.PropertiesSql +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), CAST(SERVERPROPERTY('Collation') AS nvarchar(128)), + CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)), CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: InstanceConfigurationCollector.TempdbSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.TempdbSql +SELECT name, type_desc, CAST(size AS bigint) / 128, growth, is_percent_growth, max_size +FROM sys.master_files WHERE database_id = 2 ORDER BY file_id +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere +-- Kilde: InstanceConfigurationCollector.ColumnsSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.ColumnsSql +SELECT COL_LENGTH('sys.dm_server_services', 'instant_file_initialization_enabled'), COL_LENGTH('sys.dm_os_sys_info', 'sql_memory_model_desc') +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(med IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(true) +SELECT servicename, service_account, startup_type_desc, status_desc, instant_file_initialization_enabled FROM sys.dm_server_services +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(uden IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(false) +SELECT servicename, service_account, startup_type_desc, status_desc, CAST(NULL AS nvarchar(1)) FROM sys.dm_server_services +GO + +-- Variant: Builds med kolonnen sql_memory_model_desc (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.MemoryModelSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.MemoryModelSql +SELECT sql_memory_model_desc FROM sys.dm_os_sys_info +GO diff --git a/SQL Scripts/SQLI-CPU-001.sql b/SQL Scripts/SQLI-CPU-001.sql new file mode 100644 index 0000000..990f5f5 --- /dev/null +++ b/SQL Scripts/SQLI-CPU-001.sql @@ -0,0 +1,77 @@ +-- =================================================================================================================== +-- SQLI-CPU-001: Vedvarende højt CPU-forbrug +-- =================================================================================================================== +-- Regel-id: SQLI-CPU-001 +-- Titel: Vedvarende højt CPU-forbrug +-- Alvor: SQL Server: Medium fra 75 %, High fra 90 %. Andre processer: Medium. +-- Versioner: SQL Server 2005 og nyere. "Andre processer" kan ikke måles på Linux (host_platform fra SQL Server +-- 2017) og Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om CPU'en har været vedvarende høj, opdelt i SQL Servers eget forbrug og andre processers. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Scheduler monitor skriver én post i minuttet (ProcessUtilization = SQL Server, SystemIdle = ledig). Reglen bruger +-- de seneste op til 15 poster (kræver mindst 5). Fund 1: gennemsnitlig ProcessUtilization >= 75 %. Fund 2: +-- gennemsnitlig andel til andre processer (100 - SystemIdle - ProcessUtilization) >= 25 %. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 15 minutter (CpuRule.Minutes); 75 % / 90 % for SQL Server; 25 % for andre (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): +-- CpuCollector +-- Fil: src/SQLInsight.Collectors/Performance/CpuCollector.cs +-- Medlemmer: CpuCollector.Sql, CpuCollector.HostPlatformSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:cpu) +-- Delt script: SQL Scripts/_collectors/CpuCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : CpuRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- CpuCollector: SQL Servers og systemets CPU-forbrug pr. minut (scheduler monitor ring buffer, op til 60 minutter). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: CpuCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.Sql +DECLARE @now_ticks bigint; +SELECT @now_ticks = ms_ticks FROM sys.dm_os_sys_info; +SELECT TOP (60) + DATEADD(second, CAST((rb.[timestamp] - @now_ticks) / 1000 AS int), GETUTCDATE()), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int'), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') +FROM ( + SELECT [timestamp], CONVERT(xml, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%%' +) rb +ORDER BY rb.[timestamp] DESC +GO + +-- Variant: SQL Server 2017 og nyere (Windows eller Linux) +-- Kilde: CpuCollector.HostPlatformSql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.HostPlatformSql +SELECT host_platform FROM sys.dm_os_host_info +GO diff --git a/SQL Scripts/SQLI-CPU-002.sql b/SQL Scripts/SQLI-CPU-002.sql new file mode 100644 index 0000000..0cf30f5 --- /dev/null +++ b/SQL Scripts/SQLI-CPU-002.sql @@ -0,0 +1,130 @@ +-- =================================================================================================================== +-- 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 diff --git a/SQL Scripts/SQLI-DB-001.sql b/SQL Scripts/SQLI-DB-001.sql new file mode 100644 index 0000000..c093fc4 --- /dev/null +++ b/SQL Scripts/SQLI-DB-001.sql @@ -0,0 +1,222 @@ +-- =================================================================================================================== +-- SQLI-DB-001: Database er ikke tilgængelig +-- =================================================================================================================== +-- Regel-id: SQLI-DB-001 +-- Titel: Database er ikke tilgængelig +-- Alvor: Critical for SUSPECT, RECOVERY_PENDING og EMERGENCY; Low for OFFLINE. +-- Versioner: Alle (SQL Server 2000 via DATABASEPROPERTYEX(..., 'Status')). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en database er utilgængelig. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. database (undtagen tempdb), hvis state_desc er SUSPECT, RECOVERY_PENDING, EMERGENCY eller OFFLINE. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : DatabaseStateRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-002.sql b/SQL Scripts/SQLI-DB-002.sql new file mode 100644 index 0000000..3787899 --- /dev/null +++ b/SQL Scripts/SQLI-DB-002.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-002: AUTO_SHRINK er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-DB-002 +-- Titel: AUTO_SHRINK er slået til +-- Alvor: High +-- Versioner: Alle (SQL Server 2000 og nyere). +-- +-- Hvad tjekkes, og hvorfor: +-- Om AUTO_SHRINK er slået til. Det fragmenterer indexes og koster CPU og I/O i en evig cyklus. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE database med is_auto_shrink_on = 1. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule +-- "SQLI-DB-002") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-003.sql b/SQL Scripts/SQLI-DB-003.sql new file mode 100644 index 0000000..bd4761c --- /dev/null +++ b/SQL Scripts/SQLI-DB-003.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-003: AUTO_CLOSE er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-DB-003 +-- Titel: AUTO_CLOSE er slået til +-- Alvor: Medium +-- Versioner: Alle (SQL Server 2000 og nyere). +-- +-- Hvad tjekkes, og hvorfor: +-- Om AUTO_CLOSE er slået til, så databasen lukkes og plan cache tømmes, når sidste forbindelse slipper den. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE database med is_auto_close_on = 1. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule +-- "SQLI-DB-003") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-004.sql b/SQL Scripts/SQLI-DB-004.sql new file mode 100644 index 0000000..f262918 --- /dev/null +++ b/SQL Scripts/SQLI-DB-004.sql @@ -0,0 +1,224 @@ +-- =================================================================================================================== +-- SQLI-DB-004: Sidekontrol er ikke CHECKSUM +-- =================================================================================================================== +-- Regel-id: SQLI-DB-004 +-- Titel: Sidekontrol er ikke CHECKSUM +-- Alvor: Medium +-- Versioner: Alle (på SQL Server 2000 rapporteres TORN_PAGE_DETECTION eller NONE, da CHECKSUM først findes fra +-- 2005). +-- +-- Hvad tjekkes, og hvorfor: +-- Om sidekontrollen (PAGE_VERIFY) er andet end CHECKSUM, så korruption fra storage ikke opdages ved læsning. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE database, hvor page_verify_option_desc ikke er NULL og ikke er CHECKSUM. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule +-- "SQLI-DB-004") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-005.sql b/SQL Scripts/SQLI-DB-005.sql new file mode 100644 index 0000000..e025711 --- /dev/null +++ b/SQL Scripts/SQLI-DB-005.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-005: TRUSTWORTHY er slået til +-- =================================================================================================================== +-- Regel-id: SQLI-DB-005 +-- Titel: TRUSTWORTHY er slået til +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere (på 2000 rapporterer collectoren altid 0). +-- +-- Hvad tjekkes, og hvorfor: +-- Om TRUSTWORTHY er slået til, hvilket kan lade en db_owner blive sysadmin. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE database med is_trustworthy_on = 1, undtagen msdb (hvor det er Microsofts standard). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SimpleDatabaseRules.Create (DatabaseSettingRule +-- "SQLI-DB-005") +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-006.sql b/SQL Scripts/SQLI-DB-006.sql new file mode 100644 index 0000000..e59d4ec --- /dev/null +++ b/SQL Scripts/SQLI-DB-006.sql @@ -0,0 +1,247 @@ +-- =================================================================================================================== +-- SQLI-DB-006: Database kører med et ældre compatibility level +-- =================================================================================================================== +-- Regel-id: SQLI-DB-006 +-- Titel: Database kører med et ældre compatibility level +-- Alvor: Low +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en brugerdatabase kører med et ældre compatibility level end instansens eget. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Instansens niveau = hovedversion (fra ProductVersion i InstanceProbe) x 10, fx 160 for SQL Server 2022. Ét fund +-- pr. ONLINE brugerdatabase (ikke master, model, msdb, tempdb) med compatibility_level under det. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : CompatibilityLevelRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-DB-007.sql b/SQL Scripts/SQLI-DB-007.sql new file mode 100644 index 0000000..74ee4c2 --- /dev/null +++ b/SQL Scripts/SQLI-DB-007.sql @@ -0,0 +1,225 @@ +-- =================================================================================================================== +-- SQLI-DB-007: Database mangler en nylig fuld backup +-- =================================================================================================================== +-- Regel-id: SQLI-DB-007 +-- Titel: Database mangler en nylig fuld backup +-- Alvor: Critical hvis aldrig backet op; High hvis ældre end 7 dage; Medium hvis databasen er i en availability +-- group (backup kan være taget på en anden replika). +-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance (Azure tager backup). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en database mangler en nylig fuld backup. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver ONLINE database, der ikke er mirror/AG-sekundær, ikke er model og er mindst 1 dag gammel: fund hvis +-- seneste fulde backup (msdb.dbo.backupset type 'D', taget efter databasens create_date - 1 sekund, omregnet til +-- UTC) mangler eller er ældre end 7 dage. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 7 dage (FullBackupRule.MaxAge, fast i koden); databaser yngre end 1 dag springes over. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FullBackupRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-008.sql b/SQL Scripts/SQLI-DB-008.sql new file mode 100644 index 0000000..ee45498 --- /dev/null +++ b/SQL Scripts/SQLI-DB-008.sql @@ -0,0 +1,225 @@ +-- =================================================================================================================== +-- SQLI-DB-008: Database i FULL recovery uden log-backups +-- =================================================================================================================== +-- Regel-id: SQLI-DB-008 +-- Titel: Database i FULL recovery uden log-backups +-- Alvor: High; Medium hvis databasen er i en availability group. +-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en database i FULL eller BULK_LOGGED recovery mangler log-backups, så loggen vokser og point-in-time restore +-- går tabt. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund for ONLINE databaser i FULL/BULK_LOGGED, der ikke er mirror/AG-sekundær, ikke er read-only, ikke er model og +-- HAR haft en fuld backup (før den første fulde backup trunkeres loggen som i SIMPLE), når seneste log-backup +-- (backupset type 'L') mangler eller er ældre end 24 timer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 24 timer (LogBackupRule.MaxAge, 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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : LogBackupRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-009.sql b/SQL Scripts/SQLI-DB-009.sql new file mode 100644 index 0000000..e69069c --- /dev/null +++ b/SQL Scripts/SQLI-DB-009.sql @@ -0,0 +1,225 @@ +-- =================================================================================================================== +-- SQLI-DB-009: DBCC CHECKDB er ikke kørt for nylig +-- =================================================================================================================== +-- Regel-id: SQLI-DB-009 +-- Titel: DBCC CHECKDB er ikke kørt for nylig +-- Alvor: High hvis aldrig kørt eller ældre end 30 dage; ellers Medium. +-- Versioner: SQL Server 2016 SP2 og nyere (DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime')). Ældre versioner: "tjek +-- manuelt" (DBCC DBINFO ... WITH TABLERESULTS, dbi_dbccLastKnownGood, kræver sysadmin). Ikke relevant på +-- Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om DBCC CHECKDB ikke er kørt fejlfrit for nylig. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver ONLINE database, der er mindst 7 dage gammel og har en LastGoodCheckDbTime: fund hvis tiden er +-- 1900-01-01 (aldrig) eller ældre end 7 dage. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 7 dage (CheckDbRule.MaxAge); High over 30 dage (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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : CheckDbRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-010.sql b/SQL Scripts/SQLI-DB-010.sql new file mode 100644 index 0000000..0475d6d --- /dev/null +++ b/SQL Scripts/SQLI-DB-010.sql @@ -0,0 +1,56 @@ +-- =================================================================================================================== +-- SQLI-DB-010: Korrupte sider registreret i suspect_pages +-- =================================================================================================================== +-- Regel-id: SQLI-DB-010 +-- Titel: Korrupte sider registreret i suspect_pages +-- Alvor: Critical +-- Versioner: SQL Server 2005 og nyere (msdb.dbo.suspect_pages). +-- +-- Hvad tjekkes, og hvorfor: +-- Om SQL Server har registreret korrupte sider (fejl 823/824/825) i msdb.dbo.suspect_pages. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Rækker, der ikke er løst (event_type 1, 2 og 3; 4 = gendannet, 5 = repareret og 7 = deallokeret tæller ikke), +-- grupperes pr. database: ét fund pr. database med op til 20 sider som målinger. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- USE msdb; GRANT SELECT ON dbo.suspect_pages TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- SuspectPageCollector +-- Fil: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs +-- Medlemmer: SuspectPageCollector.Sql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:suspect-pages) +-- Delt script: SQL Scripts/_collectors/SuspectPageCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : SuspectPagesRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- SuspectPageCollector: Sider, SQL Server har registreret som korrupte (msdb.dbo.suspect_pages). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: SuspectPageCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs : SuspectPageCollector.Sql +SELECT ISNULL(DB_NAME(database_id), CAST(database_id AS nvarchar(20))), file_id, page_id, event_type, error_count, last_update_date +FROM msdb.dbo.suspect_pages +GO diff --git a/SQL Scripts/SQLI-DB-011.sql b/SQL Scripts/SQLI-DB-011.sql new file mode 100644 index 0000000..33a2d38 --- /dev/null +++ b/SQL Scripts/SQLI-DB-011.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-011: Transaktionsloggen har for mange VLF'er +-- =================================================================================================================== +-- Regel-id: SQLI-DB-011 +-- Titel: Transaktionsloggen har for mange VLF'er +-- Alvor: Medium; High over 10.000 VLF'er. +-- Versioner: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info). Ældre: "tjek manuelt" med DBCC LOGINFO (sysadmin). +-- Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om transaktionsloggen har så mange virtuelle logfiler, at recovery, restore og log-backups bliver langsomme. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE database med flere end 1.000 VLF'er (COUNT(*) fra sys.dm_db_log_info). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 1.000 (VirtualLogFileRule.Threshold); High over 10 x grænsen (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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : VirtualLogFileRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-012.sql b/SQL Scripts/SQLI-DB-012.sql new file mode 100644 index 0000000..b0fad59 --- /dev/null +++ b/SQL Scripts/SQLI-DB-012.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-012: Uhensigtsmæssig autogrowth +-- =================================================================================================================== +-- Regel-id: SQLI-DB-012 +-- Titel: Uhensigtsmæssig autogrowth +-- Alvor: Low +-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om data- og logfiler har en autogrowth, der giver mange eller uforudsigelige vækster. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver fil på mindst 1024 MB med autogrowth > 0 i ONLINE databaser: fund hvis væksten er i procent, eller hvis +-- den er under 64 MB. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Filstørrelse >= 1024 MB; fast vækst under 64 MB (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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FileGrowthRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-013.sql b/SQL Scripts/SQLI-DB-013.sql new file mode 100644 index 0000000..6396b50 --- /dev/null +++ b/SQL Scripts/SQLI-DB-013.sql @@ -0,0 +1,223 @@ +-- =================================================================================================================== +-- SQLI-DB-013: Databasefil nærmer sig sin maksimale størrelse +-- =================================================================================================================== +-- Regel-id: SQLI-DB-013 +-- Titel: Databasefil nærmer sig sin maksimale størrelse +-- Alvor: High fra 90 %; Critical når filen har nået MAXSIZE. +-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en databasefil nærmer sig sin MAXSIZE, så ændringer snart fejler (1105/9002), selv om der er plads på disken. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund for filer i ONLINE databaser med en MAXSIZE (> 0 og < 2 TB, der regnes som ubegrænset), når størrelse >= 90 +-- % af MAXSIZE. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 90 %; MAXSIZE på 2 TB eller mere regnes som ubegrænset (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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : FileMaxSizeRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-014.sql b/SQL Scripts/SQLI-DB-014.sql new file mode 100644 index 0000000..3c4ffed --- /dev/null +++ b/SQL Scripts/SQLI-DB-014.sql @@ -0,0 +1,224 @@ +-- =================================================================================================================== +-- SQLI-DB-014: Query Store indsamler ikke længere data +-- =================================================================================================================== +-- Regel-id: SQLI-DB-014 +-- Titel: Query Store indsamler ikke længere data +-- Alvor: High ved ERROR; ellers Medium. +-- Versioner: SQL Server 2016 og nyere (QueryStore). +-- +-- Hvad tjekkes, og hvorfor: +-- Om Query Store selv er gået over i READ_ONLY eller ERROR, så der ikke indsamles nye planer. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For ONLINE brugerdatabaser, der ikke er mirror/AG-sekundær, læses sys.database_query_store_options. Fund hvis +-- desired_state_desc = READ_WRITE og actual_state_desc <> READ_WRITE. readonly_reason med bit 65536 betyder, at +-- Query Store er fuld. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : QueryStoreStateRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-DB-015.sql b/SQL Scripts/SQLI-DB-015.sql new file mode 100644 index 0000000..62995a5 --- /dev/null +++ b/SQL Scripts/SQLI-DB-015.sql @@ -0,0 +1,299 @@ +-- =================================================================================================================== +-- SQLI-DB-015: tempdb har for få eller ulige store datafiler +-- =================================================================================================================== +-- Regel-id: SQLI-DB-015 +-- Titel: tempdb har for få eller ulige store datafiler +-- Alvor: Medium for for få filer; Low for ulige store filer. +-- Versioner: Alle. Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om tempdb har for få eller ulige store datafiler, hvilket giver PAGELATCH-contention på allokeringssider. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- tempdb's datafiler (type ROWS fra Files2005/Files2000, database_id 2) og cpu_count (ServerCollector). Anbefalet = +-- min(8, logiske CPU'er). Fund 1: antal filer < anbefalet og < 4. Fund 2: mere end én fil, og størrelser eller +-- autogrowth (MB/%) er ikke ens. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Op til 8 filer, én pr. logisk CPU; under 4 filer (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+ +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- ServerCollector +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Medlemmer: ServerCollector.BuildSql(version), Db.StartTimeSql(version), ServerCollector.Sql2000 +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Delt script: SQL Scripts/_collectors/ServerCollector.sql +-- Regelkode: src/SQLInsight.Rules/Databases/DatabaseRules.cs : TempdbFilesRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- ServerCollector: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/SQLI-DLK-001.sql b/SQL Scripts/SQLI-DLK-001.sql new file mode 100644 index 0000000..b4d3cf8 --- /dev/null +++ b/SQL Scripts/SQLI-DLK-001.sql @@ -0,0 +1,79 @@ +-- =================================================================================================================== +-- SQLI-DLK-001: Deadlocks +-- =================================================================================================================== +-- Regel-id: SQLI-DLK-001 +-- Titel: Deadlocks +-- Alvor: Medium; High fra 10 deadlocks siden sidste måling. +-- Versioner: SQL Server 2008 og nyere (system_health). 2012+: event-filerne; 2008/2008 R2: ring buffer. SQL Server +-- 2005: "tjek manuelt" (trace flag 1222). +-- +-- Hvad tjekkes, og hvorfor: +-- Om der er sket deadlocks siden forrige måling. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- xml_deadlock_report-hændelser fra system_health parses i C#. Første gang tages kun det seneste døgn med; derefter +-- læses videre fra seneste fil og offset. Agentens egne deadlocks (program 'SQLInsight Pro') tælles ikke. Fund når +-- der er nye deadlocks; de seneste 3 vises med processer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen (High fra 10, 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): +-- DeadlockCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs +-- Medlemmer: DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null), DeadlockCollector.RingBufferSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:deadlocks) +-- Delt script: SQL Scripts/_collectors/DeadlockCollector.sql +-- Regelkode: src/SQLInsight.Rules/Concurrency/ConcurrencyRules.cs : DeadlockRule.Evaluate +-- +-- Read-only: IKKE STRENGT READ-ONLY. DeadlockCollector.BuildFileSql (SQL Server 2012+) opretter en midlertidig +-- tabel i tempdb (SELECT ... INTO #system_health) og sletter den igen (DROP TABLE #system_health) i +-- samme batch. Den er privat for sessionen; ingen brugerdata, indstillinger eller permanente objekter +-- ændres, men det er en skrivning i tempdb. Ring buffer-varianten (2008/2008 R2) er read-only. Øvrigt: +-- kun SELECT samt SET-sessionsindstillinger. +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DeadlockCollector: Nye deadlock-grafer (xml_deadlock_report) fra den indbyggede system_health-session. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2012 og nyere (system_health-filer; første læsning) +-- Kilde: DeadlockCollector.BuildFileSql +-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null) +SELECT object_name, file_name, file_offset, + CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data +INTO #system_health +FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL); + +SELECT event_data FROM #system_health WHERE event_data IS NOT NULL; +SELECT TOP (1) file_name, file_offset FROM #system_health ORDER BY file_name DESC, file_offset DESC; +DROP TABLE #system_health; +GO + +-- Variant: SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses +-- Kilde: DeadlockCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.RingBufferSql +SELECT CAST(t.target_data AS nvarchar(max)) +FROM sys.dm_xe_session_targets t +JOIN sys.dm_xe_sessions s ON s.address = t.event_session_address +WHERE s.name = N'system_health' AND t.target_name = N'ring_buffer' +GO diff --git a/SQL Scripts/SQLI-IDX-001.sql b/SQL Scripts/SQLI-IDX-001.sql new file mode 100644 index 0000000..272f4b5 --- /dev/null +++ b/SQL Scripts/SQLI-IDX-001.sql @@ -0,0 +1,161 @@ +-- =================================================================================================================== +-- SQLI-IDX-001: Manglende index med stor effekt +-- =================================================================================================================== +-- Regel-id: SQLI-IDX-001 +-- Titel: Manglende index med stor effekt +-- Alvor: Medium hvis forbedringsmålet er mindst 1.000.000; ellers Low. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om optimizeren gentagne gange har ønsket et index med stor effekt - vurderet kritisk mod de indexes, der allerede +-- findes. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forbedringsmål = avg_total_user_cost x (avg_user_impact / 100) x (user_seeks + user_scans). Fund for forslag med +-- mål >= 50.000 og mindst 100 seeks + scans (højst 10, største først). C# sammenligner med tabellens eksisterende +-- indexes: findes et nonclustered index med lighedskolonnerne forrest, foreslås det udvidet; mange indexes, mange +-- skrivninger, brede INCLUDE og kort oppetid nævnes. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 50.000 (MinImprovementMeasure) og 100 brug (MinUses), 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+ +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- IndexCollector +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql, +-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database, +-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database) +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Delt script: SQL Scripts/_collectors/IndexCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : MissingIndexRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. +-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/SQLI-IDX-002.sql b/SQL Scripts/SQLI-IDX-002.sql new file mode 100644 index 0000000..4efbc47 --- /dev/null +++ b/SQL Scripts/SQLI-IDX-002.sql @@ -0,0 +1,160 @@ +-- =================================================================================================================== +-- SQLI-IDX-002: Ubrugt index koster ved hver skrivning +-- =================================================================================================================== +-- Regel-id: SQLI-IDX-002 +-- Titel: Ubrugt index koster ved hver skrivning +-- Alvor: Low; Medium fra 1 GB. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om et nonclustered index vedligeholdes ved hver skrivning, men aldrig læses. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- sys.dm_db_index_usage_stats nulstilles ved genstart, så agenten følger brugen på tværs af genstarter +-- (IndexUsageTracking). Fund for nonclustered, ikke-unikke, ikke-deaktiverede indexes (ikke PK/unik constraint), +-- der er fulgt i mindst 7 dage, har 0 læsninger (seeks + scans + lookups), og har mindst 1.000 skrivninger eller +-- fylder mindst 100 MB. Højst 10. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 7 dage (IndexText.MinUptimeForUsage), 1.000 skrivninger, 100 MB (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+ +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- IndexCollector +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql, +-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database, +-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database) +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Delt script: SQL Scripts/_collectors/IndexCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : UnusedIndexRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. +-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/SQLI-IDX-003.sql b/SQL Scripts/SQLI-IDX-003.sql new file mode 100644 index 0000000..2aae392 --- /dev/null +++ b/SQL Scripts/SQLI-IDX-003.sql @@ -0,0 +1,160 @@ +-- =================================================================================================================== +-- SQLI-IDX-003: Duplikeret eller overlappende index +-- =================================================================================================================== +-- Regel-id: SQLI-IDX-003 +-- Titel: Duplikeret eller overlappende index +-- Alvor: Medium for nøjagtige dubletter; Low for overlappende. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om et index er overflødigt, fordi et andet index på samme tabel dækker det. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- I C#: et nonclustered index (ikke PK/unik constraint) er dækket af et andet, når filteret er ens, dets +-- nøglekolonner er de første nøglekolonner i det andet (samme rækkefølge), et unikt index kun dækkes af et med +-- præcis samme nøgler, og alle dets INCLUDE-kolonner findes i det andet (et clustered index dækker kun ved samme +-- nøgler). Højst 10 fund. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- IndexCollector +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql, +-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database, +-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database) +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Delt script: SQL Scripts/_collectors/IndexCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : DuplicateIndexRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. +-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/SQLI-IDX-004.sql b/SQL Scripts/SQLI-IDX-004.sql new file mode 100644 index 0000000..f2c95e6 --- /dev/null +++ b/SQL Scripts/SQLI-IDX-004.sql @@ -0,0 +1,158 @@ +-- =================================================================================================================== +-- SQLI-IDX-004: Heap med mange forwarded records +-- =================================================================================================================== +-- Regel-id: SQLI-IDX-004 +-- Titel: Heap med mange forwarded records +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en heap har mange forwarded records, så læsninger hopper rundt. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund for heaps (index_id 0) med mindst 10.000 rækker og mindst 10.000 forwarded_fetch_count +-- (sys.dm_db_index_operational_stats, siden genstart). Højst 10, flest først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 10.000 rækker og 10.000 forwarded fetches (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+ +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- IndexCollector +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql, +-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database, +-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database) +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Delt script: SQL Scripts/_collectors/IndexCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : ForwardedRecordsRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. +-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/SQLI-IO-001.sql b/SQL Scripts/SQLI-IO-001.sql new file mode 100644 index 0000000..64ef707 --- /dev/null +++ b/SQL Scripts/SQLI-IO-001.sql @@ -0,0 +1,92 @@ +-- =================================================================================================================== +-- SQLI-IO-001: Høj læselatens på drev med datafiler +-- =================================================================================================================== +-- Regel-id: SQLI-IO-001 +-- Titel: Høj læselatens på drev med datafiler +-- Alvor: Medium; High fra 50 ms. +-- Versioner: SQL Server 2005 og nyere (drev/mount point fra sys.dm_os_volume_stats fra 2008 R2 SP1; før det udledt +-- af filstien). +-- +-- Hvad tjekkes, og hvorfor: +-- Om læsninger fra datafilerne på et drev er langsomme. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger af sys.dm_io_virtual_file_stats (nulstilles ved genstart). Datafiler (ROWS) +-- grupperes pr. drev: fund pr. drev, hvis der er mindst 1.000 læsninger i intervallet og gennemsnittet +-- (io_stall_read_ms / num_of_reads) er mindst 20 ms. De tre langsomste filer står i målingerne. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- >= 1.000 læsninger; >= 20 ms; High fra 50 ms (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+ +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- FileIoCollector +-- Fil: src/SQLInsight.Collectors/Performance/FileIoCollector.cs +-- Medlemmer: Db.StartTimeSql(version), FileIoCollector.SqlWithVolumes, FileIoCollector.Sql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:file-io) +-- Delt script: SQL Scripts/_collectors/FileIoCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : DataFileReadLatencyRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- FileIoCollector: Kumulativ I/O og I/O-ventetid pr. data- og logfil, med drev/mount point. Reglerne bruger +-- forskellen mellem to målinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: FileIoCollector.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: FileIoCollector.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 R2 SP1 og nyere (med drev/mount point) +-- Kilde: FileIoCollector.SqlWithVolumes +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.SqlWithVolumes +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + vs.volume_mount_point +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +OUTER APPLY sys.dm_os_volume_stats(vfs.database_id, vfs.file_id) vs +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2005 – 2008 R2 RTM +-- Kilde: FileIoCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.Sql +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + CAST(NULL AS nvarchar(260)) +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO diff --git a/SQL Scripts/SQLI-IO-002.sql b/SQL Scripts/SQLI-IO-002.sql new file mode 100644 index 0000000..d7c45df --- /dev/null +++ b/SQL Scripts/SQLI-IO-002.sql @@ -0,0 +1,90 @@ +-- =================================================================================================================== +-- SQLI-IO-002: Høj skrivelatens på drev med logfiler +-- =================================================================================================================== +-- Regel-id: SQLI-IO-002 +-- Titel: Høj skrivelatens på drev med logfiler +-- Alvor: Medium; High fra 15 ms. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om skrivninger til logfilerne på et drev er langsomme, hvilket forsinker alle commits. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger. Logfiler (LOG) grupperes pr. drev: fund pr. drev, hvis der er mindst 1.000 +-- skrivninger i intervallet og gennemsnittet (io_stall_write_ms / num_of_writes) er mindst 5 ms. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- >= 1.000 skrivninger; >= 5 ms; High fra 15 ms (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+ +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- FileIoCollector +-- Fil: src/SQLInsight.Collectors/Performance/FileIoCollector.cs +-- Medlemmer: Db.StartTimeSql(version), FileIoCollector.SqlWithVolumes, FileIoCollector.Sql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:file-io) +-- Delt script: SQL Scripts/_collectors/FileIoCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : LogFileWriteLatencyRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- FileIoCollector: Kumulativ I/O og I/O-ventetid pr. data- og logfil, med drev/mount point. Reglerne bruger +-- forskellen mellem to målinger. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: FileIoCollector.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: FileIoCollector.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 R2 SP1 og nyere (med drev/mount point) +-- Kilde: FileIoCollector.SqlWithVolumes +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.SqlWithVolumes +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + vs.volume_mount_point +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +OUTER APPLY sys.dm_os_volume_stats(vfs.database_id, vfs.file_id) vs +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2005 – 2008 R2 RTM +-- Kilde: FileIoCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.Sql +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + CAST(NULL AS nvarchar(260)) +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO diff --git a/SQL Scripts/SQLI-JOB-001.sql b/SQL Scripts/SQLI-JOB-001.sql new file mode 100644 index 0000000..f6142ef --- /dev/null +++ b/SQL Scripts/SQLI-JOB-001.sql @@ -0,0 +1,76 @@ +-- =================================================================================================================== +-- SQLI-JOB-001: SQL Agent-job fejler +-- =================================================================================================================== +-- Regel-id: SQLI-JOB-001 +-- Titel: SQL Agent-job fejler +-- Alvor: High hvis jobnavnet indeholder backup, checkdb, integrity eller dbcc; ellers Medium. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om et aktivt SQL Agent-job fejlede ved seneste kørsel. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. job med enabled = 1, hvor seneste job-udfald (sysjobhistory, step_id = 0, højeste instance_id) har +-- run_status = 0 (fejlet). Seneste fejlende trin og dets besked samt antal fejl siden i går vises som målinger. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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+ +-- USE msdb; GRANT SELECT ON dbo.sysjobs TO [agent]; GRANT SELECT ON dbo.sysjobhistory TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AgentJobCollector +-- Fil: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs +-- Medlemmer: AgentJobCollector.RunningSql, AgentJobCollector.JobsSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) +-- Delt script: SQL Scripts/_collectors/AgentJobCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : FailedJobRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- AgentJobCollector: SQL Agent-jobs fra msdb: aktiv, seneste udfald, seneste fejlende trin og antal fejl det seneste +-- døgn, og om SQL Server Agent kører. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: AgentJobCollector.RunningSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.RunningSql +SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name LIKE N'SQLAgent - %' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: AgentJobCollector.JobsSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.JobsSql +DECLARE @since int; +SET @since = CONVERT(int, CONVERT(char(8), DATEADD(day, -1, GETDATE()), 112)); +SELECT j.name, j.enabled, last_run.run_status, last_run.run_date, last_run.run_time, + failed_step.step_name, LEFT(failed_step.message, 2000), failures.n +FROM msdb.dbo.sysjobs j +OUTER APPLY (SELECT TOP (1) h.run_status, h.run_date, h.run_time FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 ORDER BY h.instance_id DESC) last_run +OUTER APPLY (SELECT TOP (1) h.step_name, h.message FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id > 0 AND h.run_status = 0 ORDER BY h.instance_id DESC) failed_step +OUTER APPLY (SELECT COUNT(*) AS n FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 AND h.run_status = 0 AND h.run_date >= @since) failures +GO diff --git a/SQL Scripts/SQLI-JOB-002.sql b/SQL Scripts/SQLI-JOB-002.sql new file mode 100644 index 0000000..5b9c5e0 --- /dev/null +++ b/SQL Scripts/SQLI-JOB-002.sql @@ -0,0 +1,101 @@ +-- =================================================================================================================== +-- SQLI-JOB-002: SQL Server Agent kører ikke +-- =================================================================================================================== +-- Regel-id: SQLI-JOB-002 +-- Titel: SQL Server Agent kører ikke +-- Alvor: High når der er aktive jobs; ellers Low. +-- Versioner: SQL Server 2005 og nyere. Bestået på Express (EngineEdition fra InstanceProbe), som ikke har SQL +-- Server Agent. +-- +-- Hvad tjekkes, og hvorfor: +-- Om SQL Server Agent ikke kører, så ingen planlagte jobs (backups!) bliver udført. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Agenten regnes som kørende, hvis der findes mindst én session med program_name LIKE 'SQLAgent - %'. Fund hvis +-- ingen findes, og udgaven ikke er Express. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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+ +-- USE msdb; GRANT SELECT ON dbo.sysjobs TO [agent]; GRANT SELECT ON dbo.sysjobhistory TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- AgentJobCollector +-- Fil: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs +-- Medlemmer: AgentJobCollector.RunningSql, AgentJobCollector.JobsSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) +-- Delt script: SQL Scripts/_collectors/AgentJobCollector.sql +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Availability/AvailabilityRules.cs : AgentStoppedRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- AgentJobCollector: SQL Agent-jobs fra msdb: aktiv, seneste udfald, seneste fejlende trin og antal fejl det seneste +-- døgn, og om SQL Server Agent kører. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: AgentJobCollector.RunningSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.RunningSql +SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name LIKE N'SQLAgent - %' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: AgentJobCollector.JobsSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.JobsSql +DECLARE @since int; +SET @since = CONVERT(int, CONVERT(char(8), DATEADD(day, -1, GETDATE()), 112)); +SELECT j.name, j.enabled, last_run.run_status, last_run.run_date, last_run.run_time, + failed_step.step_name, LEFT(failed_step.message, 2000), failures.n +FROM msdb.dbo.sysjobs j +OUTER APPLY (SELECT TOP (1) h.run_status, h.run_date, h.run_time FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 ORDER BY h.instance_id DESC) last_run +OUTER APPLY (SELECT TOP (1) h.step_name, h.message FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id > 0 AND h.run_status = 0 ORDER BY h.instance_id DESC) failed_step +OUTER APPLY (SELECT COUNT(*) AS n FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 AND h.run_status = 0 AND h.run_date >= @since) failures +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-LOG-001.sql b/SQL Scripts/SQLI-LOG-001.sql new file mode 100644 index 0000000..a5ffef9 --- /dev/null +++ b/SQL Scripts/SQLI-LOG-001.sql @@ -0,0 +1,118 @@ +-- =================================================================================================================== +-- SQLI-LOG-001: SQL Server har læst beskadigede sider (fejl 823/824/825) +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-001 +-- Titel: SQL Server har læst beskadigede sider (fejl 823/824/825) +-- Alvor: Critical for 823/824; High for 825. +-- Versioner: SQL Server 2005 og nyere. Virker med alle tre kilder (fejllog, default trace, exception ring buffer). +-- +-- Hvad tjekkes, og hvorfor: +-- Om SQL Server har læst beskadigede sider (fejl 823, 824) eller måtte læse en side igen for at få den (825). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fejlloggen læses fra forrige måling (første gang den seneste time) fra den bedste kilde, agentens rettigheder +-- giver: fejlloggen (securityadmin), default trace (ALTER TRACE) eller exception ring buffer (VIEW SERVER STATE). +-- Fejl grupperes pr. fejlnummer; et fund pr. fejlnummer 823/824/825 set inden for de seneste 24 timer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Fundet holdes åbent 24 timer efter seneste forekomst (ErrorLogAnalysis.RecentWindow, 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : CorruptionErrorRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-LOG-002.sql b/SQL Scripts/SQLI-LOG-002.sql new file mode 100644 index 0000000..a008a29 --- /dev/null +++ b/SQL Scripts/SQLI-LOG-002.sql @@ -0,0 +1,118 @@ +-- =================================================================================================================== +-- SQLI-LOG-002: I/O-forespørgsler, der tager over 15 sekunder (fejl 833) +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-002 +-- Titel: I/O-forespørgsler, der tager over 15 sekunder (fejl 833) +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. Kræver at fejlloggen selv kan læses (xp_readerrorlog, serverrollen +-- securityadmin): fejl 833 står hverken i default trace eller ring buffer, så ellers er reglen "ikke +-- målbar". +-- +-- Hvad tjekkes, og hvorfor: +-- Om storage har brugt over 15 sekunder på I/O-forespørgsler (fejl 833). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Som SQLI-LOG-001, men kun med kilden "fejlloggen": et fund, når fejl 833 er set inden for de seneste 24 timer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 24 timer (ErrorLogAnalysis.RecentWindow). +-- +-- 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : SlowIoErrorRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-LOG-003.sql b/SQL Scripts/SQLI-LOG-003.sql new file mode 100644 index 0000000..1648c7a --- /dev/null +++ b/SQL Scripts/SQLI-LOG-003.sql @@ -0,0 +1,117 @@ +-- =================================================================================================================== +-- SQLI-LOG-003: Alvorlige fejl i SQL Servers fejllog (severity 17 og derover) +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-003 +-- Titel: Alvorlige fejl i SQL Servers fejllog (severity 17 og derover) +-- Alvor: High fra severity 20; ellers Medium. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om fejlloggen indeholder alvorlige fejl (severity 17 og derover), som ikke er korruption, langsom I/O, mislykkede +-- logins, stack dumps eller AG-fejl (de har egne regler). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fejl grupperes pr. fejlnummer; ét fund pr. fejlnummer med severity >= 17 set inden for de seneste 24 timer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Severity >= 17; 24 timer. +-- +-- 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : SevereErrorRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-LOG-004.sql b/SQL Scripts/SQLI-LOG-004.sql new file mode 100644 index 0000000..bcd3d90 --- /dev/null +++ b/SQL Scripts/SQLI-LOG-004.sql @@ -0,0 +1,118 @@ +-- =================================================================================================================== +-- SQLI-LOG-004: Mange mislykkede logins +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-004 +-- Titel: Mange mislykkede logins +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere (fra ring buffer kun 2008+ via connectivity ring buffer). +-- +-- Hvad tjekkes, og hvorfor: +-- Om der er mange mislykkede logins (fejl 18456) på kort tid: forkert kodeord i en applikation eller et forsøg på +-- at gætte kodeord. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Kun den seneste måling (ikke døgnets sum): fund hvis antallet af fejl 18456 siden forrige måling (normalt 5 +-- minutter) er mindst grænsen. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:ErrorLog:LoginFailureBurst, standard 20. +-- +-- 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : LoginFailureBurstRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-LOG-005.sql b/SQL Scripts/SQLI-LOG-005.sql new file mode 100644 index 0000000..a9aabaf --- /dev/null +++ b/SQL Scripts/SQLI-LOG-005.sql @@ -0,0 +1,116 @@ +-- =================================================================================================================== +-- SQLI-LOG-005: SQL Server har lavet et stack dump +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-005 +-- Titel: SQL Server har lavet et stack dump +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. Kræver at fejlloggen selv kan læses (securityadmin); ellers "ikke målbar". +-- +-- Hvad tjekkes, og hvorfor: +-- Om SQL Server har lavet et stack dump (intern fejl, access violation, assertion). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fejl 17065, 17066, 17067, 17310 eller tekst med "Stack Dump" i fejlloggen inden for de seneste 24 timer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 24 timer. +-- +-- 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : StackDumpRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-LOG-006.sql b/SQL Scripts/SQLI-LOG-006.sql new file mode 100644 index 0000000..cec1a0c --- /dev/null +++ b/SQL Scripts/SQLI-LOG-006.sql @@ -0,0 +1,117 @@ +-- =================================================================================================================== +-- SQLI-LOG-006: Fejl i Always On availability groups +-- =================================================================================================================== +-- Regel-id: SQLI-LOG-006 +-- Titel: Fejl i Always On availability groups +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere (relevant fra 2012). +-- +-- Hvad tjekkes, og hvorfor: +-- Om fejlloggen viser fejl fra Always On availability groups. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fejl 1480, 19406, 41131, 41142, 41143, 41144, 35201-35299, eller tekst med "availability group" og severity >= 16 +-- eller "failed", inden for de seneste 24 timer. Ét fund pr. fejlnummer. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 24 timer. +-- +-- 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ErrorLogCollector +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Medlemmer: ErrorLogCollector.CanReadErrorLogSql, ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal), +-- PrivilegeCollector.DefaultTracePathSql, ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal), +-- ErrorLogCollector.RingBufferSql, ErrorLogCollector.ConnectivitySql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Delt script: SQL Scripts/_collectors/ErrorLogCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ErrorLogRules.cs : AvailabilityGroupErrorRule +-- +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ErrorLogCollector: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) exception- og +-- connectivity ring buffer (kun fejlnumre). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/SQLI-MEM-001.sql b/SQL Scripts/SQLI-MEM-001.sql new file mode 100644 index 0000000..e41df4e --- /dev/null +++ b/SQL Scripts/SQLI-MEM-001.sql @@ -0,0 +1,137 @@ +-- =================================================================================================================== +-- SQLI-MEM-001: Forespørgsler venter på hukommelse +-- =================================================================================================================== +-- Regel-id: SQLI-MEM-001 +-- Titel: Forespørgsler venter på hukommelse +-- Alvor: High +-- Versioner: Alle (SQL Server 2000 via master.dbo.sysperfinfo). +-- +-- Hvad tjekkes, og hvorfor: +-- Om forespørgsler står i kø for at få tildelt hukommelse (RESOURCE_SEMAPHORE), før de kan starte. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis tælleren 'Memory Grants Pending' (øjebliksværdi) > 0, eller RESOURCE_SEMAPHORE har ventet mindst 10.000 +-- ms i intervallet mellem to ventetidsmålinger. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Pending > 0 eller >= 10.000 ms RESOURCE_SEMAPHORE (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- PerformanceCounterCollector +-- Fil: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs +-- Medlemmer: Db.StartTimeSql(version), PerformanceCounterCollector.BuildSql(dmv: true), +-- PerformanceCounterCollector.BuildSql(dmv: false) +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) +-- Delt script: SQL Scripts/_collectors/PerformanceCounterCollector.sql +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/ResourceRules.cs : MemoryGrantsPendingRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PerformanceCounterCollector: Udvalgte performance counters: memory grants, page life expectancy, forbindelser, +-- blokerede processer, batch requests og kompileringer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: PerformanceCounterCollector.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: PerformanceCounterCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: PerformanceCounterCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PerformanceCounterCollector.BuildSql(dmv) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: true) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM sys.dm_os_performance_counters +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: PerformanceCounterCollector.BuildSql(sysperfinfo) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: false) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM master.dbo.sysperfinfo +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-OPS-001.sql b/SQL Scripts/SQLI-OPS-001.sql new file mode 100644 index 0000000..095c3a0 --- /dev/null +++ b/SQL Scripts/SQLI-OPS-001.sql @@ -0,0 +1,66 @@ +-- =================================================================================================================== +-- SQLI-OPS-001: Backup eller restore kører markant længere end normalt +-- =================================================================================================================== +-- Regel-id: SQLI-OPS-001 +-- Titel: Backup eller restore kører markant længere end normalt +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en backup eller restore kører markant længere end samme kommando på samme database plejer. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Hvert minut læses de kørende operationer med percent_complete > 0. Afsluttede operationer på mindst 1 minut +-- huskes (de seneste 10 pr. kommando og database). Fund for kørende BACKUP/RESTORE, når der er mindst 3 tidligere +-- kørsler, og den har kørt mere end 2 x medianen og mindst 5 minutter længere end medianen. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Faktor 2 (SlowBackupRule.Factor); mindst 5 minutter over medianen; mindst 3 tidligere kørsler (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): +-- LongOperationCollector +-- Fil: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs +-- Medlemmer: LongOperationCollector.Sql +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:long-operations) +-- Delt script: SQL Scripts/_collectors/LongOperationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/OperationRules.cs : SlowBackupRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- LongOperationCollector: Operationer, SQL Server rapporterer fremdrift for (BACKUP, RESTORE, DBCC, ALTER INDEX +-- REORGANIZE, SHRINK, ROLLBACK ...). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: LongOperationCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs : LongOperationCollector.Sql +SELECT r.session_id, r.command, DB_NAME(r.database_id), s.login_name, + DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), + CAST(r.percent_complete AS float), + CASE WHEN r.estimated_completion_time > 0 THEN DATEADD(second, CAST(r.estimated_completion_time / 1000 AS int), GETUTCDATE()) END, + GETUTCDATE() +FROM sys.dm_exec_requests r +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +WHERE r.percent_complete > 0 AND r.session_id <> @@SPID +GO diff --git a/SQL Scripts/SQLI-PLAN-001.sql b/SQL Scripts/SQLI-PLAN-001.sql new file mode 100644 index 0000000..cef2880 --- /dev/null +++ b/SQL Scripts/SQLI-PLAN-001.sql @@ -0,0 +1,127 @@ +-- =================================================================================================================== +-- SQLI-PLAN-001: Implicit konvertering forhindrer index seek +-- =================================================================================================================== +-- Regel-id: SQLI-PLAN-001 +-- Titel: Implicit konvertering forhindrer index seek +-- Alvor: Medium +-- Versioner: SQL Server 2005 SP2 og nyere (sys.dm_exec_text_query_plan). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en tung forespørgsel har en implicit konvertering, der forhindrer index seek. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- De 50 sætninger med mest CPU (kørt det seneste døgn) får deres cachede plan analyseret i C# (ShowplanAnalyzer; +-- planer over 4 MB springes over): fund hvis planen har PlanAffectingConvert med ConvertIssue = "Seek Plan". Højst +-- 10 fund, mest CPU først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Top 50 (PlanAnalysisCollector.TopStatements); 10 fund (PlanRule.MaxFindingsPerRule), 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): +-- PlanAnalysisCollector +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ImplicitConversionRule +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som +-- analyseres i C# (ShowplanAnalyzer). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/SQLI-PLAN-002.sql b/SQL Scripts/SQLI-PLAN-002.sql new file mode 100644 index 0000000..f749ce0 --- /dev/null +++ b/SQL Scripts/SQLI-PLAN-002.sql @@ -0,0 +1,126 @@ +-- =================================================================================================================== +-- SQLI-PLAN-002: Join uden join-betingelse (kartesisk produkt) +-- =================================================================================================================== +-- Regel-id: SQLI-PLAN-002 +-- Titel: Join uden join-betingelse (kartesisk produkt) +-- Alvor: High +-- Versioner: SQL Server 2005 SP2 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en tung forespørgsel joiner uden join-betingelse (utilsigtet kartesisk produkt). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Planen analyseres i C#: fund hvis den har en Warnings-node med NoJoinPredicate = true, og sætningsteksten ikke +-- indeholder CROSS JOIN (bevidst). Højst 10 fund. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Top 50 sætninger; 10 fund (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): +-- PlanAnalysisCollector +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : NoJoinPredicateRule +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som +-- analyseres i C# (ShowplanAnalyzer). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/SQLI-PLAN-003.sql b/SQL Scripts/SQLI-PLAN-003.sql new file mode 100644 index 0000000..37285ff --- /dev/null +++ b/SQL Scripts/SQLI-PLAN-003.sql @@ -0,0 +1,126 @@ +-- =================================================================================================================== +-- SQLI-PLAN-003: Skalar funktion kaldes for hver række +-- =================================================================================================================== +-- Regel-id: SQLI-PLAN-003 +-- Titel: Skalar funktion kaldes for hver række +-- Alvor: Medium +-- Versioner: SQL Server 2005 SP2 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en tung forespørgsel kalder en skalar brugerfunktion for hver række. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Planen analyseres i C#: fund hvis den indeholder UserDefinedFunction-noder, og sætningens samlede CPU-tid er +-- mindst 60 sekunder. Højst 10 fund. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 60.000.000 µs CPU (ScalarUdfRule.MinTotalWorkerTimeUs, 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): +-- PlanAnalysisCollector +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ScalarUdfRule +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som +-- analyseres i C# (ShowplanAnalyzer). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/SQLI-PLAN-004.sql b/SQL Scripts/SQLI-PLAN-004.sql new file mode 100644 index 0000000..841e61c --- /dev/null +++ b/SQL Scripts/SQLI-PLAN-004.sql @@ -0,0 +1,126 @@ +-- =================================================================================================================== +-- SQLI-PLAN-004: Sorteringer og hash joins spilder til tempdb +-- =================================================================================================================== +-- Regel-id: SQLI-PLAN-004 +-- Titel: Sorteringer og hash joins spilder til tempdb +-- Alvor: Medium +-- Versioner: SQL Server 2016 SP2 / 2017 CU3 og nyere (total_spills i sys.dm_exec_query_stats). Ældre: "tjek +-- manuelt". +-- +-- Hvad tjekkes, og hvorfor: +-- Om sorteringer og hash joins i en tung forespørgsel spilder til tempdb. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis total_spills / execution_count >= 1.024 sider (ca. 8 MB) pr. udførelse. Højst 10 fund. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 1.024 sider pr. udførelse (SpillRule.MinSpillPagesPerExecution, 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): +-- PlanAnalysisCollector +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : SpillRule +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som +-- analyseres i C# (ShowplanAnalyzer). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/SQLI-PLAN-005.sql b/SQL Scripts/SQLI-PLAN-005.sql new file mode 100644 index 0000000..9ded23e --- /dev/null +++ b/SQL Scripts/SQLI-PLAN-005.sql @@ -0,0 +1,126 @@ +-- =================================================================================================================== +-- SQLI-PLAN-005: Forespørgsel får langt mere hukommelse, end den bruger +-- =================================================================================================================== +-- Regel-id: SQLI-PLAN-005 +-- Titel: Forespørgsel får langt mere hukommelse, end den bruger +-- Alvor: Medium +-- Versioner: SQL Server 2016 SP2 / 2017 CU3 og nyere (total_grant_kb, total_used_grant_kb). Ældre: "tjek manuelt". +-- +-- Hvad tjekkes, og hvorfor: +-- Om en forespørgsel får tildelt langt mere hukommelse, end den bruger. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis gennemsnitlig tildeling (total_grant_kb / execution_count) >= 100 MB, og brugt (total_used_grant_kb) <= +-- 10 % af tildelt. Højst 10 fund. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 102.400 KB pr. udførelse (ExcessiveGrantRule.MinAverageGrantKb) og 10 % (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): +-- PlanAnalysisCollector +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Medlemmer: PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true), +-- PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Delt script: SQL Scripts/_collectors/PlanAnalysisCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/PlanRules.cs : ExcessiveGrantRule +-- +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PlanAnalysisCollector: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som +-- analyseres i C# (ShowplanAnalyzer). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/SQLI-QS-001.sql b/SQL Scripts/SQLI-QS-001.sql new file mode 100644 index 0000000..1d206aa --- /dev/null +++ b/SQL Scripts/SQLI-QS-001.sql @@ -0,0 +1,116 @@ +-- =================================================================================================================== +-- SQLI-QS-001: Planregression: forespørgsel har fået en langsommere plan +-- =================================================================================================================== +-- Regel-id: SQLI-QS-001 +-- Titel: Planregression: forespørgsel har fået en langsommere plan +-- Alvor: Medium; High når ekstra tid er mindst 10 minutter i døgnet. +-- Versioner: SQL Server 2016 og nyere (Query Store), i databaser med Query Store slået til. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en forespørgsel har fået en ny plan det seneste døgn, der er markant langsommere end dens bedste plan ugen +-- før. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Collectoren henter de 100 tungeste queries (samlet varighed over 8 dage) pr. database, opdelt i seneste døgn og +-- ugen før. Bedste plan = laveste gns. varighed før det seneste døgn med mindst 10 udførelser. Fund når en anden +-- plan i seneste døgn med mindst 10 udførelser er mindst 2 x langsommere og koster mindst 60 s ekstra i alt ((gns. +-- ny - gns. bedste) x udførelser). Højst 10 fund, største ekstra tid først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 10 udførelser, faktor 2, 60 s ekstra (PlanRegressionRule-konstanter, fast i koden); 100 queries pr. database +-- (QueryStoreCollector.TopQueries). +-- +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- QueryStoreCollector +-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs +-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database), +-- QueryStoreCollector.BuildFailedForcedSql(database) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store) +-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : PlanRegressionRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage +-- og forcerede planer, der fejler. +-- ------------------------------------------------------------------------------------------------------------------- + +-- 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 diff --git a/SQL Scripts/SQLI-QS-002.sql b/SQL Scripts/SQLI-QS-002.sql new file mode 100644 index 0000000..4786c1f --- /dev/null +++ b/SQL Scripts/SQLI-QS-002.sql @@ -0,0 +1,111 @@ +-- =================================================================================================================== +-- SQLI-QS-002: Forceret plan kan ikke bruges +-- =================================================================================================================== +-- Regel-id: SQLI-QS-002 +-- Titel: Forceret plan kan ikke bruges +-- Alvor: High +-- Versioner: SQL Server 2016 og nyere (Query Store). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en plan, der er forceret i Query Store, ikke længere kan bruges (fx fordi et index er fjernet). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. plan med is_forced_plan = 1 og force_failure_count > 0 (højst 10). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- QueryStoreCollector +-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs +-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database), +-- QueryStoreCollector.BuildFailedForcedSql(database) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store) +-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : FailedForcedPlanRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage +-- og forcerede planer, der fejler. +-- ------------------------------------------------------------------------------------------------------------------- + +-- 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 diff --git a/SQL Scripts/SQLI-QS-003.sql b/SQL Scripts/SQLI-QS-003.sql new file mode 100644 index 0000000..335f9f7 --- /dev/null +++ b/SQL Scripts/SQLI-QS-003.sql @@ -0,0 +1,115 @@ +-- =================================================================================================================== +-- SQLI-QS-003: Parameterfølsom forespørgsel (parameter sniffing) +-- =================================================================================================================== +-- Regel-id: SQLI-QS-003 +-- Titel: Parameterfølsom forespørgsel (parameter sniffing) +-- Alvor: Medium +-- Versioner: SQL Server 2016 og nyere (Query Store). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en forespørgsel har meget svingende svartider eller skifter mellem meget forskellige planer (parameter +-- sniffing). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver query i seneste døgn, planer med mindst 10 udførelser. Fund hvis (a) mindst 2 planer, hvor den +-- langsomste i gennemsnit er mindst 10 x den hurtigste og mindst 100 ms, eller (b) den mest brugte plan har mindst +-- 100 udførelser, max-varighed >= 1 s, max/gns. >= 20 og max stdev/gns. >= 2. Højst 10 fund, mest samlet varighed +-- først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 100 udførelser og 1 s (ParameterSensitivityRule-konstanter) samt faktorerne 10, 20 og 2 (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+ +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- QueryStoreCollector +-- Fil: src/SQLInsight.Collectors/Performance/QueryStoreCollector.cs +-- Medlemmer: QueryStoreCollector.DatabasesSql, QueryStoreCollector.BuildStatsSql(database), +-- QueryStoreCollector.BuildFailedForcedSql(database) +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:query-store) +-- Delt script: SQL Scripts/_collectors/QueryStoreCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/QueryStoreRules.cs : ParameterSensitivityRule.Evaluate +-- +-- 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 (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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- QueryStoreCollector: Query Store i hver brugerdatabase, hvor det er slået til: de 100 tungeste queries over 8 dage +-- og forcerede planer, der fejler. +-- ------------------------------------------------------------------------------------------------------------------- + +-- 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 diff --git a/SQL Scripts/SQLI-REPL-001.sql b/SQL Scripts/SQLI-REPL-001.sql new file mode 100644 index 0000000..38231b3 --- /dev/null +++ b/SQL Scripts/SQLI-REPL-001.sql @@ -0,0 +1,88 @@ +-- =================================================================================================================== +-- SQLI-REPL-001: Replikering fejler +-- =================================================================================================================== +-- Regel-id: SQLI-REPL-001 +-- Titel: Replikering fejler +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. Kun relevant på en distributør (sys.databases.is_distributor = 1); kræver +-- SELECT på tre tabeller i distributionsdatabasen, ellers "ikke målbar" med vejledning. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en distributionsagent er fejlet eller bliver ved med at prøve igen. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- For hver distributionsagent læses seneste historikpost (MSdistribution_history, højeste timestamp). Fund hvis +-- runstatus er 5 (retry) eller 6 (fejl). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Kun på en distributør, valgfrit (gives ikke af grant-least-privilege.sql): +-- USE distribution; CREATE USER [agent] FOR LOGIN [agent]; +-- GRANT SELECT ON dbo.MSdistribution_agents TO [agent]; GRANT SELECT ON dbo.MSdistribution_history TO [agent]; GRANT SELECT ON dbo.MSrepl_errors TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ReplicationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs +-- Medlemmer: ReplicationCollector.DistributionDatabaseSql, Db.CurrentDatabaseSql, +-- ReplicationCollector.AgentsSql, ReplicationCollector.ErrorsSql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:replication) +-- Delt script: SQL Scripts/_collectors/ReplicationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/ReplicationRules.cs : ReplicationAgentFailedRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ReplicationCollector: På en distributør: hver distributionsagents seneste status, forsinkelse og besked samt +-- replikeringsfejl det seneste døgn. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ReplicationCollector.DistributionDatabaseSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.DistributionDatabaseSql +SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0 +GO + +-- Variant: Kun på en distributør: husk databasen, der skal skiftes tilbage til +-- Kilde: ReplicationCollector.CurrentDatabaseSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql +SELECT DB_NAME() +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.AgentsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.AgentsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency, + CAST(h.comments AS nvarchar(500)) +FROM dbo.MSdistribution_agents a +OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x + WHERE x.agent_id = a.id ORDER BY x.timestamp DESC) h +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.ErrorsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.ErrorsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT COUNT(*) FROM dbo.MSrepl_errors WHERE time > DATEADD(hour, -24, GETDATE()) +GO diff --git a/SQL Scripts/SQLI-REPL-002.sql b/SQL Scripts/SQLI-REPL-002.sql new file mode 100644 index 0000000..4b83dda --- /dev/null +++ b/SQL Scripts/SQLI-REPL-002.sql @@ -0,0 +1,86 @@ +-- =================================================================================================================== +-- SQLI-REPL-002: Replikering er forsinket +-- =================================================================================================================== +-- Regel-id: SQLI-REPL-002 +-- Titel: Replikering er forsinket +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere, kun på en distributør (som SQLI-REPL-001). +-- +-- Hvad tjekkes, og hvorfor: +-- Om ændringer tager lang tid om at nå abonnenten. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund for agenter, der ikke er fejlet, hvor delivery_latency i seneste historikpost er mindst 5 minutter. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 300.000 ms (ReplicationSnapshot.LatencyThresholdMs, fast i koden). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Kun på en distributør, valgfrit (gives ikke af grant-least-privilege.sql): +-- USE distribution; CREATE USER [agent] FOR LOGIN [agent]; +-- GRANT SELECT ON dbo.MSdistribution_agents TO [agent]; GRANT SELECT ON dbo.MSdistribution_history TO [agent]; GRANT SELECT ON dbo.MSrepl_errors TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- ReplicationCollector +-- Fil: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs +-- Medlemmer: ReplicationCollector.DistributionDatabaseSql, Db.CurrentDatabaseSql, +-- ReplicationCollector.AgentsSql, ReplicationCollector.ErrorsSql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:replication) +-- Delt script: SQL Scripts/_collectors/ReplicationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Availability/ReplicationRules.cs : ReplicationLatencyRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ReplicationCollector: På en distributør: hver distributionsagents seneste status, forsinkelse og besked samt +-- replikeringsfejl det seneste døgn. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: ReplicationCollector.DistributionDatabaseSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.DistributionDatabaseSql +SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0 +GO + +-- Variant: Kun på en distributør: husk databasen, der skal skiftes tilbage til +-- Kilde: ReplicationCollector.CurrentDatabaseSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql +SELECT DB_NAME() +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.AgentsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.AgentsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency, + CAST(h.comments AS nvarchar(500)) +FROM dbo.MSdistribution_agents a +OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x + WHERE x.agent_id = a.id ORDER BY x.timestamp DESC) h +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.ErrorsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.ErrorsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT COUNT(*) FROM dbo.MSrepl_errors WHERE time > DATEADD(hour, -24, GETDATE()) +GO diff --git a/SQL Scripts/SQLI-REQ-001.sql b/SQL Scripts/SQLI-REQ-001.sql new file mode 100644 index 0000000..cd220f7 --- /dev/null +++ b/SQL Scripts/SQLI-REQ-001.sql @@ -0,0 +1,93 @@ +-- =================================================================================================================== +-- SQLI-REQ-001: Sætning har kørt længe +-- =================================================================================================================== +-- Regel-id: SQLI-REQ-001 +-- Titel: Sætning har kørt længe +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en brugersætning har kørt længe og måske holder låse, som andre venter på. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- SQL'en finder requests med total_elapsed_time over grænsen og percent_complete = 0. I C# fjernes BACKUP, RESTORE +-- og DBCC (de er langvarige operationer) og requests, der venter med en godartet ventetype (WAITFOR, Service Broker +-- m.fl.). Fund for op til 10, længst kørende først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Activity:RequestMinutes, standard 15 (minutter) = 900000 ms i SQL'en. +-- +-- 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): +-- ActivityCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs +-- Medlemmer: ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt, +-- ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:activity) +-- Delt script: SQL Scripts/_collectors/ActivityCollector.sql +-- Regelkode: src/SQLInsight.Rules/Concurrency/ActivityRules.cs : LongRunningStatementRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ActivityCollector: Åbne brugertransaktioner (alder, log) og brugersætninger, der har kørt længere end grænsen. +-- Agentens egne sessioner er udeladt. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log) +-- Kilde: ActivityCollector.BuildTransactionsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt +SELECT TOP 100 t.session_id, t.transaction_id, t.name, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), t.begin_time), + t.used, t.reserved, t.db, t.dbs, s.login_name, s.host_name, s.program_name, s.status, r.wait_type, + CAST(CASE WHEN r.sql_handle IS NOT NULL + THEN SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) + ELSE LEFT(q.text, 1000) END AS nvarchar(1000)) +FROM (SELECT st.session_id, at.transaction_id, MAX(at.name) AS name, MIN(at.transaction_begin_time) AS begin_time, + ISNULL(SUM(dt.database_transaction_log_bytes_used), 0) AS used, ISNULL(SUM(dt.database_transaction_log_bytes_reserved), 0) AS reserved, + MIN(DB_NAME(dt.database_id)) AS db, COUNT(DISTINCT dt.database_id) AS dbs + FROM sys.dm_tran_session_transactions st + JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id + LEFT JOIN sys.dm_tran_database_transactions dt ON dt.transaction_id = st.transaction_id AND dt.database_id <> 32767 + GROUP BY st.session_id, at.transaction_id) t +JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +OUTER APPLY (SELECT TOP 1 x.sql_handle, x.statement_start_offset, x.statement_end_offset, x.wait_type FROM sys.dm_exec_requests x + WHERE x.session_id = t.session_id ORDER BY x.request_id) r +OUTER APPLY (SELECT TOP 1 c.most_recent_sql_handle FROM sys.dm_exec_connections c WHERE c.session_id = t.session_id) c +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND t.begin_time IS NOT NULL +ORDER BY CASE WHEN t.used + t.reserved >= 1073741824 THEN 0 ELSE 1 END, t.begin_time +GO + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter) +-- Kilde: ActivityCollector.BuildLongRequestsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +SELECT TOP 50 r.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), r.total_elapsed_time, r.command, r.status, + r.wait_type, r.wait_time, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, r.blocking_session_id, + CAST(SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(1000)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND r.total_elapsed_time > 900000 AND r.percent_complete = 0 +ORDER BY r.total_elapsed_time DESC +GO diff --git a/SQL Scripts/SQLI-SEC-001.sql b/SQL Scripts/SQLI-SEC-001.sql new file mode 100644 index 0000000..c281c11 --- /dev/null +++ b/SQL Scripts/SQLI-SEC-001.sql @@ -0,0 +1,106 @@ +-- =================================================================================================================== +-- SQLI-SEC-001: Ny forhøjet rettighed på serveren +-- =================================================================================================================== +-- Regel-id: SQLI-SEC-001 +-- Titel: Ny forhøjet rettighed på serveren +-- Alvor: High for sysadmin, securityadmin og CONTROL SERVER; Medium for de øvrige faste serverroller. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om nogen har fået sysadmin eller en anden forhøjet serverrettighed - direkte eller ved at blive meldt ind i en +-- Windows-gruppe, der har den. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Medlemmer af de faste serverroller (sysadmin, securityadmin, serveradmin, setupadmin, processadmin, diskadmin, +-- dbcreator, bulkadmin) og indehavere af CONTROL SERVER (GRANT/WITH GRANT) sammenlignes med forrige måling; +-- Windows-grupper foldes ud til personer via Active Directory af agenten (ikke SQL). Nye rettigheder inden for de +-- seneste 7 dage giver ét fund hver. Hvem og hvornår slås op i default trace (EventClass 104, 105, 108) fra en time +-- før forrige måling - kun med ALTER TRACE; uden står "ukendt". +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Fundet holdes åbent i 7 dage (PrivilegeAnalysis.RecentWindow, fast i koden). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- PrivilegeCollector +-- Fil: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs +-- Medlemmer: PrivilegeCollector.MembersSql, PrivilegeCollector.SqlLoginsSql, +-- PrivilegeCollector.DefaultTraceEnabledSql, PrivilegeCollector.DefaultTracePathSql, +-- PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:privileges) +-- Delt script: SQL Scripts/_collectors/PrivilegeCollector.sql +-- Regelkode: src/SQLInsight.Rules/Security/PrivilegeRules.cs : NewPrivilegeRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt sys.fn_trace_gettable (læser default trace), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PrivilegeCollector: Medlemmer af de faste serverroller og indehavere af CONTROL SERVER, SQL-logins' +-- password-politik og - med ALTER TRACE - hvem der ændrede rettighederne (default trace). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.MembersSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.MembersSql +SELECT p.name, p.type, r.name, p.is_disabled +FROM sys.server_role_members m +JOIN sys.server_principals r ON r.principal_id = m.role_principal_id +JOIN sys.server_principals p ON p.principal_id = m.member_principal_id +WHERE r.name IN (N'sysadmin', N'securityadmin', N'serveradmin', N'setupadmin', N'processadmin', N'diskadmin', N'dbcreator', N'bulkadmin') +UNION ALL +SELECT p.name, p.type, N'CONTROL SERVER', p.is_disabled +FROM sys.server_permissions sp +JOIN sys.server_principals p ON p.principal_id = sp.grantee_principal_id +WHERE sp.permission_name = N'CONTROL SERVER' AND sp.state IN ('G', 'W') +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.SqlLoginsSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.SqlLoginsSql +SELECT name, is_disabled, is_policy_checked, is_expiration_checked, CASE WHEN principal_id = 1 THEN 1 ELSE 0 END +FROM sys.sql_logins WHERE name NOT LIKE N'##%' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.DefaultTraceEnabledSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTraceEnabledSql +SELECT CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'default trace enabled' +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.BuildTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal) +-- Agenten indsætter default trace-filens sti (mappen + log.trc, så alle rollover-filer læses) og serverens lokale tid +-- for en time før forrige måling som literaler. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (104, 105, 108) AND StartTime >= @siden +GO diff --git a/SQL Scripts/SQLI-SEC-002.sql b/SQL Scripts/SQLI-SEC-002.sql new file mode 100644 index 0000000..da92be5 --- /dev/null +++ b/SQL Scripts/SQLI-SEC-002.sql @@ -0,0 +1,104 @@ +-- =================================================================================================================== +-- SQLI-SEC-002: For mange personer og konti med sysadmin +-- =================================================================================================================== +-- Regel-id: SQLI-SEC-002 +-- Titel: For mange personer og konti med sysadmin +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om flere personer og konti har sysadmin end kundens egen grænse. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Aktive konti med sysadmin tælles: SQL- og Windows-logins direkte (ikke tjeneste-identiteter som NT SERVICE\..., +-- NT AUTHORITY\..., ##MS_...## og konti, der ender på $) og de aktive personer i Windows-grupper med sysadmin +-- (foldet ud via Active Directory af agenten). Fund hvis antallet > grænsen. Grupper, der ikke kunne foldes ud, +-- nævnes i målingerne. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Privileges:MaxSysadminAccounts, standard 3. +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- PrivilegeCollector +-- Fil: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs +-- Medlemmer: PrivilegeCollector.MembersSql, PrivilegeCollector.SqlLoginsSql, +-- PrivilegeCollector.DefaultTraceEnabledSql, PrivilegeCollector.DefaultTracePathSql, +-- PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:privileges) +-- Delt script: SQL Scripts/_collectors/PrivilegeCollector.sql +-- Regelkode: src/SQLInsight.Rules/Security/PrivilegeRules.cs : TooManySysadminsRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt sys.fn_trace_gettable (læser default trace), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- PrivilegeCollector: Medlemmer af de faste serverroller og indehavere af CONTROL SERVER, SQL-logins' +-- password-politik og - med ALTER TRACE - hvem der ændrede rettighederne (default trace). +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.MembersSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.MembersSql +SELECT p.name, p.type, r.name, p.is_disabled +FROM sys.server_role_members m +JOIN sys.server_principals r ON r.principal_id = m.role_principal_id +JOIN sys.server_principals p ON p.principal_id = m.member_principal_id +WHERE r.name IN (N'sysadmin', N'securityadmin', N'serveradmin', N'setupadmin', N'processadmin', N'diskadmin', N'dbcreator', N'bulkadmin') +UNION ALL +SELECT p.name, p.type, N'CONTROL SERVER', p.is_disabled +FROM sys.server_permissions sp +JOIN sys.server_principals p ON p.principal_id = sp.grantee_principal_id +WHERE sp.permission_name = N'CONTROL SERVER' AND sp.state IN ('G', 'W') +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.SqlLoginsSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.SqlLoginsSql +SELECT name, is_disabled, is_policy_checked, is_expiration_checked, CASE WHEN principal_id = 1 THEN 1 ELSE 0 END +FROM sys.sql_logins WHERE name NOT LIKE N'##%' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.DefaultTraceEnabledSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTraceEnabledSql +SELECT CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'default trace enabled' +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.BuildTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal) +-- Agenten indsætter default trace-filens sti (mappen + log.trc, så alle rollover-filer læses) og serverens lokale tid +-- for en time før forrige måling som literaler. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (104, 105, 108) AND StartTime >= @siden +GO diff --git a/SQL Scripts/SQLI-STAT-001.sql b/SQL Scripts/SQLI-STAT-001.sql new file mode 100644 index 0000000..9662cdd --- /dev/null +++ b/SQL Scripts/SQLI-STAT-001.sql @@ -0,0 +1,159 @@ +-- =================================================================================================================== +-- SQLI-STAT-001: Forældede statistikker på store tabeller +-- =================================================================================================================== +-- Regel-id: SQLI-STAT-001 +-- Titel: Forældede statistikker på store tabeller +-- Alvor: Low +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om statistikker på store tabeller er forældede eller aldrig opdateret. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fra sys.sysindexes (rowmodctr, som kun kræver metadata-adgang): fund hvis STATS_DATE er NULL og tabellen har +-- rækker, eller tabellen har mindst 10.000 rækker og rowmodctr >= 20 % af rækkerne. Højst 10, aldrig opdaterede +-- først. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 10.000 rækker (MinRows) og 20 % (MinModifiedShare), 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+ +-- 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]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- IndexCollector +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Medlemmer: Db.StartTimeSql(version), IndexCollector.DatabasesSql, IndexCollector.MissingSql, +-- IndexCollector.BuildIndexSql(database, filteredIndexes: true), IndexCollector.BuildIndexSql(database, +-- filteredIndexes: false), IndexCollector.BuildStatisticsSql(database) +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Delt script: SQL Scripts/_collectors/IndexCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : StaleStatisticsRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- IndexCollector: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. +-- Kun metadata og DMV'er; ingen tabeldata, ingen scanning. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/SQLI-STAT-002.sql b/SQL Scripts/SQLI-STAT-002.sql new file mode 100644 index 0000000..eb3a92f --- /dev/null +++ b/SQL Scripts/SQLI-STAT-002.sql @@ -0,0 +1,222 @@ +-- =================================================================================================================== +-- SQLI-STAT-002: Automatisk statistik er slået fra +-- =================================================================================================================== +-- Regel-id: SQLI-STAT-002 +-- Titel: Automatisk statistik er slået fra +-- Alvor: Medium +-- Versioner: Alle (SQL Server 2000 via DATABASEPROPERTYEX). +-- +-- Hvad tjekkes, og hvorfor: +-- Om automatisk oprettelse eller opdatering af statistik er slået fra. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. ONLINE, ikke read-only database, hvor is_auto_create_stats_on = 0 eller is_auto_update_stats_on = 0. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- DatabaseCollector +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Medlemmer: DatabaseCollector.BuildSql2005(instance, capabilities), DatabaseCollector.BuildSql2000(), +-- DatabaseCollector.Files2005, DatabaseCollector.Files2000, Db.UtcOffsetSql, DatabaseCollector.VlfSql, +-- DatabaseCollector.BuildQueryStoreSql(database) +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Delt script: SQL Scripts/_collectors/DatabaseCollector.sql +-- Regelkode: src/SQLInsight.Rules/Indexes/IndexRules.cs : AutoStatisticsRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- DatabaseCollector: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, +-- Query Store-tilstand og filer. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/SQLI-SVC-001.sql b/SQL Scripts/SQLI-SVC-001.sql new file mode 100644 index 0000000..924be33 --- /dev/null +++ b/SQL Scripts/SQLI-SVC-001.sql @@ -0,0 +1,118 @@ +-- =================================================================================================================== +-- SQLI-SVC-001: En SQL Server-tjeneste, der skal starte automatisk, kører ikke +-- =================================================================================================================== +-- Regel-id: SQLI-SVC-001 +-- Titel: En SQL Server-tjeneste, der skal starte automatisk, kører ikke +-- Alvor: High +-- Versioner: SQL Server 2008 R2 SP1 og nyere (sys.dm_server_services). +-- +-- Hvad tjekkes, og hvorfor: +-- Om en SQL Server-tjeneste (fx SQL Server Agent, Full-text launcher, Launchpad), der er sat til at starte +-- automatisk, ikke kører. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ét fund pr. tjeneste i sys.dm_server_services med startup_type_desc = Automatic og status_desc <> Running. Kunne +-- tjenesterne ikke læses: "ikke målbar". +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Ingen. +-- +-- 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 +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- InstanceConfigurationCollector +-- Fil: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs +-- Medlemmer: InstanceConfigurationCollector.PropertiesSql, InstanceConfigurationCollector.TempdbSql, +-- InstanceConfigurationCollector.ColumnsSql, InstanceConfigurationCollector.BuildServicesSql(true), +-- InstanceConfigurationCollector.BuildServicesSql(false), InstanceConfigurationCollector.MemoryModelSql +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:instance-config) +-- Delt script: SQL Scripts/_collectors/InstanceConfigurationCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/ServiceRules.cs : StoppedServiceRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceConfigurationCollector: Instansens opsætning til konfigurationssiden og ændringsloggen: egenskaber, +-- tempdb-filer, tjenester og konti, Instant File Initialization, memory model, sp_configure og trace flags. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- Variant: Alle versioner (SQL Server 2000 og nyere) (samme forespørgsel som TraceFlagCollector) +-- Kilde: TraceFlagCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql +DBCC TRACESTATUS(-1) WITH NO_INFOMSGS +GO + +-- Variant: Alle versioner +-- Kilde: InstanceConfigurationCollector.PropertiesSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.PropertiesSql +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), CAST(SERVERPROPERTY('Collation') AS nvarchar(128)), + CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)), CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: InstanceConfigurationCollector.TempdbSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.TempdbSql +SELECT name, type_desc, CAST(size AS bigint) / 128, growth, is_percent_growth, max_size +FROM sys.master_files WHERE database_id = 2 ORDER BY file_id +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere +-- Kilde: InstanceConfigurationCollector.ColumnsSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.ColumnsSql +SELECT COL_LENGTH('sys.dm_server_services', 'instant_file_initialization_enabled'), COL_LENGTH('sys.dm_os_sys_info', 'sql_memory_model_desc') +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(med IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(true) +SELECT servicename, service_account, startup_type_desc, status_desc, instant_file_initialization_enabled FROM sys.dm_server_services +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(uden IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(false) +SELECT servicename, service_account, startup_type_desc, status_desc, CAST(NULL AS nvarchar(1)) FROM sys.dm_server_services +GO + +-- Variant: Builds med kolonnen sql_memory_model_desc (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.MemoryModelSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.MemoryModelSql +SELECT sql_memory_model_desc FROM sys.dm_os_sys_info +GO diff --git a/SQL Scripts/SQLI-TEMP-001.sql b/SQL Scripts/SQLI-TEMP-001.sql new file mode 100644 index 0000000..0aa85b3 --- /dev/null +++ b/SQL Scripts/SQLI-TEMP-001.sql @@ -0,0 +1,105 @@ +-- =================================================================================================================== +-- SQLI-TEMP-001: Version store i tempdb er stor eller vokser +-- =================================================================================================================== +-- Regel-id: SQLI-TEMP-001 +-- Titel: Version store i tempdb er stor eller vokser +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om version store i tempdb er stor eller vokser støt - typisk fordi en lang transaktion holder gamle +-- rækkeversioner i live. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Version store-størrelse = tælleren 'Version Store Size (KB)' (ellers version_store_reserved_page_count x 8 KB). +-- Fund hvis størrelsen >= grænsen, eller hvis den er vokset i hver af de seneste 6 målinger og samlet med mindst +-- 256 MB. Over halvdelen af grænsen spørges sys.dm_tran_top_version_generators (højst én gang i timen), hvilke +-- databaser der laver versionerne. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Tempdb:VersionStoreMegabytes, standard 1024 (MB); vækst over 6 målinger og 256 MB +-- (TempdbAnalysis.IsGrowing, 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): +-- TempdbCollector +-- Fil: src/SQLInsight.Collectors/Instance/TempdbCollector.cs +-- Medlemmer: TempdbCollector.SpaceSql, TempdbCollector.SessionsSql, TempdbCollector.OldestSnapshotSql, +-- TempdbCollector.GeneratorsSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:tempdb) +-- Delt script: SQL Scripts/_collectors/TempdbCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/TempdbRules.cs : VersionStoreRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- TempdbCollector: tempdb: fri plads, version store, bruger- og interne objekter, de tungeste sessioner, ældste +-- snapshot-transaktion og hvilke databaser der laver rækkeversioner. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SpaceSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SpaceSql +SELECT SUM(CAST(u.unallocated_extent_page_count AS bigint)), SUM(CAST(u.version_store_reserved_page_count AS bigint)), + SUM(CAST(u.user_object_reserved_page_count AS bigint)), SUM(CAST(u.internal_object_reserved_page_count AS bigint)), + (SELECT SUM(CAST(f.size AS bigint)) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CASE WHEN f.growth > 0 AND (f.max_size = -1 OR f.size < f.max_size) THEN 1 ELSE 0 END) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CAST(p.cntr_value AS bigint)) FROM sys.dm_os_performance_counters p + WHERE p.counter_name = N'Version Store Size (KB)' AND p.object_name LIKE N'%Transactions%') +FROM tempdb.sys.dm_db_file_space_usage u +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SessionsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SessionsSql +SELECT TOP 5 u.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), s.login_time), u.pages, s.login_name, s.program_name, s.host_name, s.status +FROM (SELECT a.session_id, SUM(a.uo_alloc - a.uo_dealloc + a.io_alloc - a.io_dealloc) AS pages + FROM (SELECT session_id, CAST(user_objects_alloc_page_count AS bigint) AS uo_alloc, CAST(user_objects_dealloc_page_count AS bigint) AS uo_dealloc, + CAST(internal_objects_alloc_page_count AS bigint) AS io_alloc, CAST(internal_objects_dealloc_page_count AS bigint) AS io_dealloc + FROM sys.dm_db_session_space_usage + UNION ALL + SELECT session_id, CAST(user_objects_alloc_page_count AS bigint), CAST(user_objects_dealloc_page_count AS bigint), + CAST(internal_objects_alloc_page_count AS bigint), CAST(internal_objects_dealloc_page_count AS bigint) + FROM sys.dm_db_task_space_usage) a + GROUP BY a.session_id) u +JOIN sys.dm_exec_sessions s ON s.session_id = u.session_id +WHERE s.is_user_process = 1 AND u.pages > 0 +ORDER BY u.pages DESC +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.OldestSnapshotSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.OldestSnapshotSql +SELECT TOP 1 t.session_id, t.elapsed_time_seconds, s.login_name, s.program_name +FROM sys.dm_tran_active_snapshot_database_transactions t +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +ORDER BY t.elapsed_time_seconds DESC +GO + +-- Variant: SQL Server 2005 og nyere, kun når version store er over halvdelen af grænsen, højst én gang i timen +-- Kilde: TempdbCollector.GeneratorsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.GeneratorsSql +SELECT TOP 10 DB_NAME(database_id), SUM(CAST(aggregated_record_length_in_bytes AS bigint)) +FROM sys.dm_tran_top_version_generators +GROUP BY database_id +ORDER BY SUM(CAST(aggregated_record_length_in_bytes AS bigint)) DESC +GO diff --git a/SQL Scripts/SQLI-TEMP-002.sql b/SQL Scripts/SQLI-TEMP-002.sql new file mode 100644 index 0000000..0b6e3f0 --- /dev/null +++ b/SQL Scripts/SQLI-TEMP-002.sql @@ -0,0 +1,101 @@ +-- =================================================================================================================== +-- SQLI-TEMP-002: tempdb er næsten fuld +-- =================================================================================================================== +-- Regel-id: SQLI-TEMP-002 +-- Titel: tempdb er næsten fuld +-- Alvor: High +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om tempdb er næsten fuld og ikke kan vokse, så sorteringer, temp-tabeller og version store snart fejler. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund hvis ingen af tempdb's datafiler kan vokse (growth > 0 og size < max_size eller max_size = -1) og fri plads +-- (unallocated_extent_page_count) er under 10 % af datafilernes størrelse. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 10 % fri (TempdbFullRule.FreePercent, 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): +-- TempdbCollector +-- Fil: src/SQLInsight.Collectors/Instance/TempdbCollector.cs +-- Medlemmer: TempdbCollector.SpaceSql, TempdbCollector.SessionsSql, TempdbCollector.OldestSnapshotSql, +-- TempdbCollector.GeneratorsSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:tempdb) +-- Delt script: SQL Scripts/_collectors/TempdbCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/TempdbRules.cs : TempdbFullRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- TempdbCollector: tempdb: fri plads, version store, bruger- og interne objekter, de tungeste sessioner, ældste +-- snapshot-transaktion og hvilke databaser der laver rækkeversioner. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SpaceSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SpaceSql +SELECT SUM(CAST(u.unallocated_extent_page_count AS bigint)), SUM(CAST(u.version_store_reserved_page_count AS bigint)), + SUM(CAST(u.user_object_reserved_page_count AS bigint)), SUM(CAST(u.internal_object_reserved_page_count AS bigint)), + (SELECT SUM(CAST(f.size AS bigint)) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CASE WHEN f.growth > 0 AND (f.max_size = -1 OR f.size < f.max_size) THEN 1 ELSE 0 END) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CAST(p.cntr_value AS bigint)) FROM sys.dm_os_performance_counters p + WHERE p.counter_name = N'Version Store Size (KB)' AND p.object_name LIKE N'%Transactions%') +FROM tempdb.sys.dm_db_file_space_usage u +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SessionsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SessionsSql +SELECT TOP 5 u.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), s.login_time), u.pages, s.login_name, s.program_name, s.host_name, s.status +FROM (SELECT a.session_id, SUM(a.uo_alloc - a.uo_dealloc + a.io_alloc - a.io_dealloc) AS pages + FROM (SELECT session_id, CAST(user_objects_alloc_page_count AS bigint) AS uo_alloc, CAST(user_objects_dealloc_page_count AS bigint) AS uo_dealloc, + CAST(internal_objects_alloc_page_count AS bigint) AS io_alloc, CAST(internal_objects_dealloc_page_count AS bigint) AS io_dealloc + FROM sys.dm_db_session_space_usage + UNION ALL + SELECT session_id, CAST(user_objects_alloc_page_count AS bigint), CAST(user_objects_dealloc_page_count AS bigint), + CAST(internal_objects_alloc_page_count AS bigint), CAST(internal_objects_dealloc_page_count AS bigint) + FROM sys.dm_db_task_space_usage) a + GROUP BY a.session_id) u +JOIN sys.dm_exec_sessions s ON s.session_id = u.session_id +WHERE s.is_user_process = 1 AND u.pages > 0 +ORDER BY u.pages DESC +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.OldestSnapshotSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.OldestSnapshotSql +SELECT TOP 1 t.session_id, t.elapsed_time_seconds, s.login_name, s.program_name +FROM sys.dm_tran_active_snapshot_database_transactions t +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +ORDER BY t.elapsed_time_seconds DESC +GO + +-- Variant: SQL Server 2005 og nyere, kun når version store er over halvdelen af grænsen, højst én gang i timen +-- Kilde: TempdbCollector.GeneratorsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.GeneratorsSql +SELECT TOP 10 DB_NAME(database_id), SUM(CAST(aggregated_record_length_in_bytes AS bigint)) +FROM sys.dm_tran_top_version_generators +GROUP BY database_id +ORDER BY SUM(CAST(aggregated_record_length_in_bytes AS bigint)) DESC +GO diff --git a/SQL Scripts/SQLI-TEMP-003.sql b/SQL Scripts/SQLI-TEMP-003.sql new file mode 100644 index 0000000..7c7b557 --- /dev/null +++ b/SQL Scripts/SQLI-TEMP-003.sql @@ -0,0 +1,103 @@ +-- =================================================================================================================== +-- SQLI-TEMP-003: Session bruger meget tempdb i lang tid +-- =================================================================================================================== +-- Regel-id: SQLI-TEMP-003 +-- Titel: Session bruger meget tempdb i lang tid +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en session har brugt meget tempdb i lang tid. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- SQL'en finder de 5 brugersessioner, der bruger mest tempdb (bruger- + interne objekter, allokeret - deallokeret, +-- fra session- og task-space-usage). C# husker, hvornår hver session (session_id + login_time) første gang var over +-- grænsen; fund når den har været over i mindst 15 minutter. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Tempdb:SessionMegabytes, standard 1024 (MB); 15 minutter (TempdbAnalysis.HeavySessionMinutes, 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): +-- TempdbCollector +-- Fil: src/SQLInsight.Collectors/Instance/TempdbCollector.cs +-- Medlemmer: TempdbCollector.SpaceSql, TempdbCollector.SessionsSql, TempdbCollector.OldestSnapshotSql, +-- TempdbCollector.GeneratorsSql +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:tempdb) +-- Delt script: SQL Scripts/_collectors/TempdbCollector.sql +-- Regelkode: src/SQLInsight.Rules/Instance/TempdbRules.cs : TempdbSessionRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- TempdbCollector: tempdb: fri plads, version store, bruger- og interne objekter, de tungeste sessioner, ældste +-- snapshot-transaktion og hvilke databaser der laver rækkeversioner. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SpaceSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SpaceSql +SELECT SUM(CAST(u.unallocated_extent_page_count AS bigint)), SUM(CAST(u.version_store_reserved_page_count AS bigint)), + SUM(CAST(u.user_object_reserved_page_count AS bigint)), SUM(CAST(u.internal_object_reserved_page_count AS bigint)), + (SELECT SUM(CAST(f.size AS bigint)) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CASE WHEN f.growth > 0 AND (f.max_size = -1 OR f.size < f.max_size) THEN 1 ELSE 0 END) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CAST(p.cntr_value AS bigint)) FROM sys.dm_os_performance_counters p + WHERE p.counter_name = N'Version Store Size (KB)' AND p.object_name LIKE N'%Transactions%') +FROM tempdb.sys.dm_db_file_space_usage u +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SessionsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SessionsSql +SELECT TOP 5 u.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), s.login_time), u.pages, s.login_name, s.program_name, s.host_name, s.status +FROM (SELECT a.session_id, SUM(a.uo_alloc - a.uo_dealloc + a.io_alloc - a.io_dealloc) AS pages + FROM (SELECT session_id, CAST(user_objects_alloc_page_count AS bigint) AS uo_alloc, CAST(user_objects_dealloc_page_count AS bigint) AS uo_dealloc, + CAST(internal_objects_alloc_page_count AS bigint) AS io_alloc, CAST(internal_objects_dealloc_page_count AS bigint) AS io_dealloc + FROM sys.dm_db_session_space_usage + UNION ALL + SELECT session_id, CAST(user_objects_alloc_page_count AS bigint), CAST(user_objects_dealloc_page_count AS bigint), + CAST(internal_objects_alloc_page_count AS bigint), CAST(internal_objects_dealloc_page_count AS bigint) + FROM sys.dm_db_task_space_usage) a + GROUP BY a.session_id) u +JOIN sys.dm_exec_sessions s ON s.session_id = u.session_id +WHERE s.is_user_process = 1 AND u.pages > 0 +ORDER BY u.pages DESC +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.OldestSnapshotSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.OldestSnapshotSql +SELECT TOP 1 t.session_id, t.elapsed_time_seconds, s.login_name, s.program_name +FROM sys.dm_tran_active_snapshot_database_transactions t +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +ORDER BY t.elapsed_time_seconds DESC +GO + +-- Variant: SQL Server 2005 og nyere, kun når version store er over halvdelen af grænsen, højst én gang i timen +-- Kilde: TempdbCollector.GeneratorsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.GeneratorsSql +SELECT TOP 10 DB_NAME(database_id), SUM(CAST(aggregated_record_length_in_bytes AS bigint)) +FROM sys.dm_tran_top_version_generators +GROUP BY database_id +ORDER BY SUM(CAST(aggregated_record_length_in_bytes AS bigint)) DESC +GO diff --git a/SQL Scripts/SQLI-TRAN-001.sql b/SQL Scripts/SQLI-TRAN-001.sql new file mode 100644 index 0000000..6d78cc8 --- /dev/null +++ b/SQL Scripts/SQLI-TRAN-001.sql @@ -0,0 +1,92 @@ +-- =================================================================================================================== +-- SQLI-TRAN-001: Transaktion har været åben længe +-- =================================================================================================================== +-- Regel-id: SQLI-TRAN-001 +-- Titel: Transaktion har været åben længe +-- Alvor: Medium; High når den har været åben 6 x grænsen. +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en brugertransaktion har været åben længe og holder låse og forhindrer, at loggen og version store ryddes. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Hvert minut læses op til 100 åbne transaktioner for brugersessioner (ikke agentens egne). Fund (op til 10, ældste +-- først) for transaktioner, hvis alder (målingstidspunkt - transaction_begin_time i UTC) er mindst grænsen. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Activity:TransactionMinutes, standard 10 (minutter); High fra 6 x grænsen. +-- +-- 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): +-- ActivityCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs +-- Medlemmer: ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt, +-- ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:activity) +-- Delt script: SQL Scripts/_collectors/ActivityCollector.sql +-- Regelkode: src/SQLInsight.Rules/Concurrency/ActivityRules.cs : LongTransactionRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ActivityCollector: Åbne brugertransaktioner (alder, log) og brugersætninger, der har kørt længere end grænsen. +-- Agentens egne sessioner er udeladt. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log) +-- Kilde: ActivityCollector.BuildTransactionsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt +SELECT TOP 100 t.session_id, t.transaction_id, t.name, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), t.begin_time), + t.used, t.reserved, t.db, t.dbs, s.login_name, s.host_name, s.program_name, s.status, r.wait_type, + CAST(CASE WHEN r.sql_handle IS NOT NULL + THEN SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) + ELSE LEFT(q.text, 1000) END AS nvarchar(1000)) +FROM (SELECT st.session_id, at.transaction_id, MAX(at.name) AS name, MIN(at.transaction_begin_time) AS begin_time, + ISNULL(SUM(dt.database_transaction_log_bytes_used), 0) AS used, ISNULL(SUM(dt.database_transaction_log_bytes_reserved), 0) AS reserved, + MIN(DB_NAME(dt.database_id)) AS db, COUNT(DISTINCT dt.database_id) AS dbs + FROM sys.dm_tran_session_transactions st + JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id + LEFT JOIN sys.dm_tran_database_transactions dt ON dt.transaction_id = st.transaction_id AND dt.database_id <> 32767 + GROUP BY st.session_id, at.transaction_id) t +JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +OUTER APPLY (SELECT TOP 1 x.sql_handle, x.statement_start_offset, x.statement_end_offset, x.wait_type FROM sys.dm_exec_requests x + WHERE x.session_id = t.session_id ORDER BY x.request_id) r +OUTER APPLY (SELECT TOP 1 c.most_recent_sql_handle FROM sys.dm_exec_connections c WHERE c.session_id = t.session_id) c +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND t.begin_time IS NOT NULL +ORDER BY CASE WHEN t.used + t.reserved >= 1073741824 THEN 0 ELSE 1 END, t.begin_time +GO + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter) +-- Kilde: ActivityCollector.BuildLongRequestsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +SELECT TOP 50 r.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), r.total_elapsed_time, r.command, r.status, + r.wait_type, r.wait_time, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, r.blocking_session_id, + CAST(SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(1000)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND r.total_elapsed_time > 900000 AND r.percent_complete = 0 +ORDER BY r.total_elapsed_time DESC +GO diff --git a/SQL Scripts/SQLI-TRAN-002.sql b/SQL Scripts/SQLI-TRAN-002.sql new file mode 100644 index 0000000..21e4066 --- /dev/null +++ b/SQL Scripts/SQLI-TRAN-002.sql @@ -0,0 +1,93 @@ +-- =================================================================================================================== +-- SQLI-TRAN-002: Transaktion bruger meget transaktionslog +-- =================================================================================================================== +-- Regel-id: SQLI-TRAN-002 +-- Titel: Transaktion bruger meget transaktionslog +-- Alvor: Medium +-- Versioner: SQL Server 2005 og nyere. +-- +-- Hvad tjekkes, og hvorfor: +-- Om en transaktion holder meget transaktionslog, som først kan frigives, når den afsluttes. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Fund (op til 10, største først) for transaktioner, hvor database_transaction_log_bytes_used + _reserved (summeret +-- over databaser) er mindst grænsen. Grænsen indgår også i SQL'en: transaktioner over grænsen sorteres først, så de +-- altid er blandt de 100 rækker. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- SQLInsight:Activity:TransactionLogMegabytes, standard 1024 (MB) = 1073741824 bytes i SQL'en. +-- +-- 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): +-- ActivityCollector +-- Fil: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs +-- Medlemmer: ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt, +-- ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:activity) +-- Delt script: SQL Scripts/_collectors/ActivityCollector.sql +-- Regelkode: src/SQLInsight.Rules/Concurrency/ActivityRules.cs : LargeTransactionRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- ActivityCollector: Åbne brugertransaktioner (alder, log) og brugersætninger, der har kørt længere end grænsen. +-- Agentens egne sessioner er udeladt. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log) +-- Kilde: ActivityCollector.BuildTransactionsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt +SELECT TOP 100 t.session_id, t.transaction_id, t.name, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), t.begin_time), + t.used, t.reserved, t.db, t.dbs, s.login_name, s.host_name, s.program_name, s.status, r.wait_type, + CAST(CASE WHEN r.sql_handle IS NOT NULL + THEN SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) + ELSE LEFT(q.text, 1000) END AS nvarchar(1000)) +FROM (SELECT st.session_id, at.transaction_id, MAX(at.name) AS name, MIN(at.transaction_begin_time) AS begin_time, + ISNULL(SUM(dt.database_transaction_log_bytes_used), 0) AS used, ISNULL(SUM(dt.database_transaction_log_bytes_reserved), 0) AS reserved, + MIN(DB_NAME(dt.database_id)) AS db, COUNT(DISTINCT dt.database_id) AS dbs + FROM sys.dm_tran_session_transactions st + JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id + LEFT JOIN sys.dm_tran_database_transactions dt ON dt.transaction_id = st.transaction_id AND dt.database_id <> 32767 + GROUP BY st.session_id, at.transaction_id) t +JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +OUTER APPLY (SELECT TOP 1 x.sql_handle, x.statement_start_offset, x.statement_end_offset, x.wait_type FROM sys.dm_exec_requests x + WHERE x.session_id = t.session_id ORDER BY x.request_id) r +OUTER APPLY (SELECT TOP 1 c.most_recent_sql_handle FROM sys.dm_exec_connections c WHERE c.session_id = t.session_id) c +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND t.begin_time IS NOT NULL +ORDER BY CASE WHEN t.used + t.reserved >= 1073741824 THEN 0 ELSE 1 END, t.begin_time +GO + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter) +-- Kilde: ActivityCollector.BuildLongRequestsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +SELECT TOP 50 r.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), r.total_elapsed_time, r.command, r.status, + r.wait_type, r.wait_time, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, r.blocking_session_id, + CAST(SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(1000)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND r.total_elapsed_time > 900000 AND r.percent_complete = 0 +ORDER BY r.total_elapsed_time DESC +GO diff --git a/SQL Scripts/SQLI-VER-001.sql b/SQL Scripts/SQLI-VER-001.sql new file mode 100644 index 0000000..7fa51e1 --- /dev/null +++ b/SQL Scripts/SQLI-VER-001.sql @@ -0,0 +1,71 @@ +-- =================================================================================================================== +-- SQLI-VER-001: SQL Server-versionens supportstatus +-- =================================================================================================================== +-- Regel-id: SQLI-VER-001 +-- Titel: SQL Server-versionens supportstatus +-- Alvor: Critical når extended support er udløbet; High når mainstream er udløbet og der er højst 365 dage til +-- extended support slutter; ellers Medium i extended support. +-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance (kræver +-- SelfManagedInfrastructure: Azure patcher selv). +-- +-- Hvad tjekkes, og hvorfor: +-- Om instansens SQL Server-udgivelse er ude af, eller på vej ud af, Microsofts support. En version uden support får +-- ingen sikkerhedsopdateringer. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Udgivelsen (fx SQL Server 2016) udledes af ProductVersion fra InstanceProbe. Datoerne for mainstream og extended +-- support slås op i regelpakken (src/SQLInsight.Rules/Lifecycle/support-lifecycle.json eller en nyere, downloadet +-- regelpakke). Dagens dato (UTC) > extended end: Critical. Dagens dato > mainstream end: High, hvis der er højst +-- 365 dage til extended end, ellers Medium. Ukendt version eller manglende datoer: "ikke målbar". Ingen SQL +-- beregner selve dommen. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Advarselsvindue før extended end: 365 dage (SupportLifecycleRule.ExtendedEndWarningWindow, fast i koden, ingen +-- konfigurationsnøgle). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Lifecycle/SupportLifecycleRule.cs : SupportLifecycleRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-VER-002.sql b/SQL Scripts/SQLI-VER-002.sql new file mode 100644 index 0000000..d97f447 --- /dev/null +++ b/SQL Scripts/SQLI-VER-002.sql @@ -0,0 +1,68 @@ +-- =================================================================================================================== +-- SQLI-VER-002: Instansen mangler opdateringer +-- =================================================================================================================== +-- Regel-id: SQLI-VER-002 +-- Titel: Instansen mangler opdateringer +-- Alvor: High når versionen stadig er i support; Medium når den er ude af support (så er SQLI-VER-001 det +-- vigtigste fund). +-- Versioner: Alle (SQL Server 2000 og nyere). Ikke relevant på Azure SQL Managed Instance. +-- +-- Hvad tjekkes, og hvorfor: +-- Om instansen mangler den seneste kendte cumulative update, sikkerhedsopdatering eller service pack for sin +-- udgivelse. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- ProductVersion (InstanceProbe) sammenlignes med det nyeste build for udgivelsen i regelpakkens build-katalog +-- (src/SQLInsight.Rules/Lifecycle/build-catalog.json eller downloadet regelpakke). Lavere build: fund. Er kataloget +-- ældre end 120 dage, står det i målingerne, at det kan være forældet. Kender kataloget ikke udgivelsen: "ikke +-- målbar". +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Katalogets maksimale alder før advarsel: 120 dage (PatchLevelRule.CatalogMaxAge, fast i koden). +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- InstanceProbe +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Medlemmer: InstanceProbe.Query +-- Interval: én gang, når agenten forbinder til instansen +-- Delt script: SQL Scripts/_collectors/InstanceProbe.sql +-- Regelkode: src/SQLInsight.Rules/Lifecycle/PatchLevelRule.cs : PatchLevelRule.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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- InstanceProbe: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/SQLI-WAIT-001.sql b/SQL Scripts/SQLI-WAIT-001.sql new file mode 100644 index 0000000..38d5a36 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-001.sql @@ -0,0 +1,87 @@ +-- =================================================================================================================== +-- SQLI-WAIT-001: SQL Server løber tør for worker threads +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-001 +-- Titel: SQL Server løber tør for worker threads +-- Alvor: Critical +-- Versioner: Alle (SQL Server 2000 via DBCC SQLPERF(WAITSTATS)). +-- +-- Hvad tjekkes, og hvorfor: +-- Om SQL Server er løbet tør for worker threads (THREADPOOL-ventetider): nye forbindelser hænger. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Ventetiderne er kumulative; reglen bruger forskellen mellem to målinger (normalt 5 minutter; nulstilles ved +-- genstart, hvor første måling ikke giver noget fund). Godartede/idle ventetyper fjernes (WaitCatalog.IsBenign). +-- Fund hvis THREADPOOL har ventet mindst 1.000 ms i intervallet. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 1.000 ms i intervallet (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : ThreadPoolWaitRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-002.sql b/SQL Scripts/SQLI-WAIT-002.sql new file mode 100644 index 0000000..7b70311 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-002.sql @@ -0,0 +1,87 @@ +-- =================================================================================================================== +-- SQLI-WAIT-002: Tasks venter på CPU (høj signal wait) +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-002 +-- Titel: Tasks venter på CPU (høj signal wait) +-- Alvor: Medium; High fra 40 %. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om tasks venter på CPU (høj andel signal wait), dvs. schedulers er overbelastede. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger, uden godartede ventetyper. Fund hvis summen af signal_wait_time_ms / summen af +-- wait_time_ms >= 25 %, og der i gennemsnit er mindst én ventende task (samlet ventetid / intervallets længde >= +-- 1). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- 25 %; High fra 40 %; mindst 1 ventende task i gennemsnit (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : SignalWaitRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-003.sql b/SQL Scripts/SQLI-WAIT-003.sql new file mode 100644 index 0000000..579f185 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-003.sql @@ -0,0 +1,87 @@ +-- =================================================================================================================== +-- SQLI-WAIT-003: Sessioner venter meget på låse +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-003 +-- Titel: Sessioner venter meget på låse +-- Alvor: Medium; High fra 5 ventende tasks i gennemsnit. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om sessioner venter meget på låse (LCK_M_*), dvs. blocking. +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger, uden godartede ventetyper. Fund hvis LCK_M_* udgør mindst 20 % af al ventetid og i +-- gennemsnit mindst 0,5 task venter på låse (ventetid / intervallets længde). +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Andel >= 20 %, gns. ventende >= 0,5; High fra 5 (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : WaitRules.Create (DominantWaitRule "SQLI-WAIT-003", +-- kategori Locks) +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-004.sql b/SQL Scripts/SQLI-WAIT-004.sql new file mode 100644 index 0000000..11b103d --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-004.sql @@ -0,0 +1,87 @@ +-- =================================================================================================================== +-- SQLI-WAIT-004: Forespørgsler venter meget på at læse data fra disk +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-004 +-- Titel: Forespørgsler venter meget på at læse data fra disk +-- Alvor: Medium; High fra 8 ventende tasks i gennemsnit. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om forespørgsler venter meget på at læse sider fra disk (PAGEIOLATCH_*). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger, uden godartede ventetyper. Fund hvis PAGEIOLATCH_* udgør mindst 30 % af al +-- ventetid og i gennemsnit mindst 1 task venter. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Andel >= 30 %, gns. ventende >= 1; High fra 8 (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : WaitRules.Create (DominantWaitRule "SQLI-WAIT-004", +-- kategori BufferIo) +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-005.sql b/SQL Scripts/SQLI-WAIT-005.sql new file mode 100644 index 0000000..ded98c7 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-005.sql @@ -0,0 +1,86 @@ +-- =================================================================================================================== +-- SQLI-WAIT-005: Commits venter på langsom skrivning til transaktionsloggen +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-005 +-- Titel: Commits venter på langsom skrivning til transaktionsloggen +-- Alvor: Medium; High fra 15 ms i gennemsnit. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om commits venter på langsom skrivning til transaktionsloggen (WRITELOG). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger. Fund hvis WRITELOG har mindst 1.000 waits i intervallet og gennemsnittet +-- (wait_time_ms / waiting_tasks_count) er mindst 5 ms. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- >= 1.000 waits; gns. >= 5 ms; High fra 15 ms (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : WriteLogLatencyRule.Evaluate +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-006.sql b/SQL Scripts/SQLI-WAIT-006.sql new file mode 100644 index 0000000..8420952 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-006.sql @@ -0,0 +1,88 @@ +-- =================================================================================================================== +-- SQLI-WAIT-006: Parallelle forespørgsler venter meget på hinanden +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-006 +-- Titel: Parallelle forespørgsler venter meget på hinanden +-- Alvor: Medium; High fra 16 ventende tasks i gennemsnit. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om parallelle forespørgsler venter meget på hinanden (CXPACKET, CXSYNC_PORT, CXSYNC_CONSUMER, EXCHANGE; +-- CXCONSUMER regnes som godartet). +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger, uden godartede ventetyper. Fund hvis parallelisme-ventetyperne udgør mindst 30 % +-- af al ventetid og i gennemsnit mindst 2 tasks venter. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Andel >= 30 %, gns. ventende >= 2; High fra 16 (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : WaitRules.Create (DominantWaitRule "SQLI-WAIT-006", +-- kategori Parallelism) +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/SQLI-WAIT-007.sql b/SQL Scripts/SQLI-WAIT-007.sql new file mode 100644 index 0000000..8dc8550 --- /dev/null +++ b/SQL Scripts/SQLI-WAIT-007.sql @@ -0,0 +1,88 @@ +-- =================================================================================================================== +-- SQLI-WAIT-007: Contention på sider i hukommelsen (PAGELATCH) +-- =================================================================================================================== +-- Regel-id: SQLI-WAIT-007 +-- Titel: Contention på sider i hukommelsen (PAGELATCH) +-- Alvor: Medium; High fra 4 ventende tasks i gennemsnit. +-- Versioner: Alle. +-- +-- Hvad tjekkes, og hvorfor: +-- Om der er contention på sider i hukommelsen (PAGELATCH_*), typisk tempdb-allokeringssider eller "last page +-- insert". +-- +-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt): +-- Forskellen mellem to målinger, uden godartede ventetyper. Fund hvis PAGELATCH_* udgør mindst 20 % af al ventetid +-- og i gennemsnit mindst 0,5 task venter. +-- +-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi): +-- Andel >= 20 %, gns. ventende >= 0,5; High fra 4 (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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Data kommer fra (collector, fil : medlemmer, interval): +-- WaitStatsCollector +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Medlemmer: Db.StartTimeSql(version), WaitStatsCollector.Sql2005, WaitStatsCollector.Sql2000 +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Delt script: SQL Scripts/_collectors/WaitStatsCollector.sql +-- Regelkode: src/SQLInsight.Rules/Performance/WaitRules.cs : WaitRules.Create (DominantWaitRule "SQLI-WAIT-007", +-- kategori PageLatch) +-- +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 + +-- ------------------------------------------------------------------------------------------------------------------- +-- WaitStatsCollector: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt +-- starttidspunkt) nulstiller. +-- ------------------------------------------------------------------------------------------------------------------- + +-- Variant: SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/_collectors/ActivityCollector.sql b/SQL Scripts/_collectors/ActivityCollector.sql new file mode 100644 index 0000000..7bd4471 --- /dev/null +++ b/SQL Scripts/_collectors/ActivityCollector.sql @@ -0,0 +1,58 @@ +-- =================================================================================================================== +-- Collector: ActivityCollector ("activity") +-- =================================================================================================================== +-- Hvad: Åbne brugertransaktioner (alder, log) og brugersætninger, der har kørt længere end grænsen. Agentens +-- egne sessioner er udeladt. +-- Fil: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:activity) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-TRAN-001, SQLI-TRAN-002, SQLI-REQ-001 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere (vist med standardgrænsen 1024 MB log) +-- Kilde: ActivityCollector.BuildTransactionsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt +SELECT TOP 100 t.session_id, t.transaction_id, t.name, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), t.begin_time), + t.used, t.reserved, t.db, t.dbs, s.login_name, s.host_name, s.program_name, s.status, r.wait_type, + CAST(CASE WHEN r.sql_handle IS NOT NULL + THEN SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) + ELSE LEFT(q.text, 1000) END AS nvarchar(1000)) +FROM (SELECT st.session_id, at.transaction_id, MAX(at.name) AS name, MIN(at.transaction_begin_time) AS begin_time, + ISNULL(SUM(dt.database_transaction_log_bytes_used), 0) AS used, ISNULL(SUM(dt.database_transaction_log_bytes_reserved), 0) AS reserved, + MIN(DB_NAME(dt.database_id)) AS db, COUNT(DISTINCT dt.database_id) AS dbs + FROM sys.dm_tran_session_transactions st + JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id + LEFT JOIN sys.dm_tran_database_transactions dt ON dt.transaction_id = st.transaction_id AND dt.database_id <> 32767 + GROUP BY st.session_id, at.transaction_id) t +JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +OUTER APPLY (SELECT TOP 1 x.sql_handle, x.statement_start_offset, x.statement_end_offset, x.wait_type FROM sys.dm_exec_requests x + WHERE x.session_id = t.session_id ORDER BY x.request_id) r +OUTER APPLY (SELECT TOP 1 c.most_recent_sql_handle FROM sys.dm_exec_connections c WHERE c.session_id = t.session_id) c +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND t.begin_time IS NOT NULL +ORDER BY CASE WHEN t.used + t.reserved >= 1073741824 THEN 0 ELSE 1 END, t.begin_time +GO + +-- Variant: SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter) +-- Kilde: ActivityCollector.BuildLongRequestsSql +-- Kode: src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs : ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt +SELECT TOP 50 r.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), r.total_elapsed_time, r.command, r.status, + r.wait_type, r.wait_time, DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, r.blocking_session_id, + CAST(SUBSTRING(q.text, r.statement_start_offset / 2 + 1, + CASE WHEN r.statement_end_offset = -1 THEN 1000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(1000)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) q +WHERE s.is_user_process = 1 AND s.session_id <> @@SPID AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') AND r.total_elapsed_time > 900000 AND r.percent_complete = 0 +ORDER BY r.total_elapsed_time DESC +GO diff --git a/SQL Scripts/_collectors/AgentJobCollector.sql b/SQL Scripts/_collectors/AgentJobCollector.sql new file mode 100644 index 0000000..1e0045b --- /dev/null +++ b/SQL Scripts/_collectors/AgentJobCollector.sql @@ -0,0 +1,43 @@ +-- =================================================================================================================== +-- Collector: AgentJobCollector ("agent-jobs") +-- =================================================================================================================== +-- Hvad: SQL Agent-jobs fra msdb: aktiv, seneste udfald, seneste fejlende trin og antal fejl det seneste døgn, +-- og om SQL Server Agent kører. +-- Fil: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:agent-jobs) +-- Versioner: SQL Server 2005 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+ +-- USE msdb; GRANT SELECT ON dbo.sysjobs TO [agent]; GRANT SELECT ON dbo.sysjobhistory TO [agent]; +-- +-- Bruges af: SQLI-JOB-001, SQLI-JOB-002 +-- 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 2005 og nyere +-- Kilde: AgentJobCollector.RunningSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.RunningSql +SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name LIKE N'SQLAgent - %' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: AgentJobCollector.JobsSql +-- Kode: src/SQLInsight.Collectors/Instance/AgentJobCollector.cs : AgentJobCollector.JobsSql +DECLARE @since int; +SET @since = CONVERT(int, CONVERT(char(8), DATEADD(day, -1, GETDATE()), 112)); +SELECT j.name, j.enabled, last_run.run_status, last_run.run_date, last_run.run_time, + failed_step.step_name, LEFT(failed_step.message, 2000), failures.n +FROM msdb.dbo.sysjobs j +OUTER APPLY (SELECT TOP (1) h.run_status, h.run_date, h.run_time FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 ORDER BY h.instance_id DESC) last_run +OUTER APPLY (SELECT TOP (1) h.step_name, h.message FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id > 0 AND h.run_status = 0 ORDER BY h.instance_id DESC) failed_step +OUTER APPLY (SELECT COUNT(*) AS n FROM msdb.dbo.sysjobhistory h + WHERE h.job_id = j.job_id AND h.step_id = 0 AND h.run_status = 0 AND h.run_date >= @since) failures +GO diff --git a/SQL Scripts/_collectors/AvailabilityGroupCollector.sql b/SQL Scripts/_collectors/AvailabilityGroupCollector.sql new file mode 100644 index 0000000..e2a9dc5 --- /dev/null +++ b/SQL Scripts/_collectors/AvailabilityGroupCollector.sql @@ -0,0 +1,49 @@ +-- =================================================================================================================== +-- Collector: AvailabilityGroupCollector ("availability-groups") +-- =================================================================================================================== +-- Hvad: Always On: replikaer, databasereplikaer (køer, hastigheder, commit-tid) og Windows-clusterets +-- medlemmer. +-- Fil: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:availability-groups) +-- Versioner: SQL Server 2012 og nyere med Always On slået til +-- +-- 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 +-- +-- Bruges af: SQLI-AG-003, SQLI-AG-001, SQLI-AG-002 +-- Read-only: Ja: kun SELECT samt 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 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.ReplicasSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ReplicasSql +SELECT ag.name, ar.replica_server_name, ars.is_local, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc, + ar.availability_mode_desc, ar.failover_mode_desc +FROM sys.availability_groups ag +JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til +-- Kilde: AvailabilityGroupCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.DatabasesSql +SELECT ag.name, DB_NAME(drs.database_id), ar.replica_server_name, drs.is_local, + CASE WHEN ars.role = 1 THEN 1 ELSE 0 END, + drs.synchronization_state_desc, drs.synchronization_health_desc, drs.is_suspended, drs.suspend_reason_desc, + drs.log_send_queue_size, drs.log_send_rate, drs.redo_queue_size, drs.redo_rate, drs.last_commit_time +FROM sys.dm_hadr_database_replica_states drs +JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id +JOIN sys.availability_groups ag ON ag.group_id = drs.group_id +LEFT JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = drs.replica_id +GO + +-- Variant: SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster) +-- Kilde: AvailabilityGroupCollector.ClusterMembersSql +-- Kode: src/SQLInsight.Collectors/Instance/AvailabilityGroupCollector.cs : AvailabilityGroupCollector.ClusterMembersSql +SELECT member_name, member_type_desc, member_state, member_state_desc, number_of_quorum_votes FROM sys.dm_hadr_cluster_members +GO diff --git a/SQL Scripts/_collectors/BlockingCollector.sql b/SQL Scripts/_collectors/BlockingCollector.sql new file mode 100644 index 0000000..caaf170 --- /dev/null +++ b/SQL Scripts/_collectors/BlockingCollector.sql @@ -0,0 +1,68 @@ +-- =================================================================================================================== +-- Collector: BlockingCollector ("blocking") +-- =================================================================================================================== +-- Hvad: Blokerede sessioner og deres blokerere lige nu, med sætning og åbne transaktioner. +-- Fil: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:blocking) +-- Versioner: Alle (SQL Server 2000 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Bruges af: SQLI-BLK-001 +-- Read-only: Ja: kun SELECT samt 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 2012 og nyere +-- Kilde: BlockingCollector.BuildSql2005(2012+) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(COALESCE(r.database_id, s.database_id)), s.login_name, s.host_name, s.program_name, s.open_transaction_count, + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: BlockingCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.BuildSql2005(version) +SELECT s.session_id, NULLIF(r.blocking_session_id, 0), COALESCE(r.status, s.status), r.wait_type, r.wait_time, r.wait_resource, + DB_NAME(r.database_id), s.login_name, s.host_name, s.program_name, (SELECT COUNT(*) FROM sys.dm_tran_session_transactions st WHERE st.session_id = s.session_id), + LEFT(CASE WHEN r.sql_handle IS NOT NULL AND r.statement_start_offset IS NOT NULL + THEN SUBSTRING(t.text, r.statement_start_offset / 2 + 1, + (CASE WHEN r.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END + - r.statement_start_offset) / 2 + 1) + ELSE t.text END, 4000), + s.last_request_end_time +FROM sys.dm_exec_sessions s +LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id +LEFT JOIN sys.dm_exec_connections c ON c.session_id = s.session_id +OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, c.most_recent_sql_handle)) t +WHERE r.blocking_session_id > 0 + OR s.session_id IN (SELECT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: BlockingCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs : BlockingCollector.Sql2000 +SELECT p.spid, NULLIF(CASE WHEN p.blocked = p.spid THEN 0 ELSE p.blocked END, 0), RTRIM(p.status), RTRIM(p.lastwaittype), + p.waittime, RTRIM(p.waitresource), DB_NAME(p.dbid), RTRIM(p.loginame), RTRIM(p.hostname), RTRIM(p.program_name), + p.open_tran, CAST(NULL AS nvarchar(4000)), p.last_batch +FROM master.dbo.sysprocesses p +WHERE (p.blocked > 0 AND p.blocked <> p.spid) + OR p.spid IN (SELECT blocked FROM master.dbo.sysprocesses WHERE blocked > 0 AND blocked <> spid) +GO diff --git a/SQL Scripts/_collectors/ConfigurationCollector.sql b/SQL Scripts/_collectors/ConfigurationCollector.sql new file mode 100644 index 0000000..bdc56d4 --- /dev/null +++ b/SQL Scripts/_collectors/ConfigurationCollector.sql @@ -0,0 +1,33 @@ +-- =================================================================================================================== +-- Collector: ConfigurationCollector ("configuration") +-- =================================================================================================================== +-- Hvad: sp_configure: konfigureret værdi og værdi i brug for alle serverindstillinger. +-- Fil: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:configuration) +-- Versioner: Alle (SQL Server 2000 og nyere) +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Bruges af: SQLI-CFG-001, SQLI-CFG-002, SQLI-CFG-003, SQLI-CFG-004, SQLI-CFG-005, SQLI-CFG-006, SQLI-CFG-007, +-- SQLI-CFG-008, SQLI-CFG-009, SQLI-CFG-010, SQLI-CFG-011 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO diff --git a/SQL Scripts/_collectors/CpuCollector.sql b/SQL Scripts/_collectors/CpuCollector.sql new file mode 100644 index 0000000..1d390a2 --- /dev/null +++ b/SQL Scripts/_collectors/CpuCollector.sql @@ -0,0 +1,42 @@ +-- =================================================================================================================== +-- Collector: CpuCollector ("cpu") +-- =================================================================================================================== +-- Hvad: SQL Servers og systemets CPU-forbrug pr. minut (scheduler monitor ring buffer, op til 60 minutter). +-- Fil: src/SQLInsight.Collectors/Performance/CpuCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:cpu) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-CPU-001, SQLI-ANOM-001 +-- 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 2005 og nyere +-- Kilde: CpuCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.Sql +DECLARE @now_ticks bigint; +SELECT @now_ticks = ms_ticks FROM sys.dm_os_sys_info; +SELECT TOP (60) + DATEADD(second, CAST((rb.[timestamp] - @now_ticks) / 1000 AS int), GETUTCDATE()), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int'), + rb.record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') +FROM ( + SELECT [timestamp], CONVERT(xml, record) AS record + FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%%' +) rb +ORDER BY rb.[timestamp] DESC +GO + +-- Variant: SQL Server 2017 og nyere (Windows eller Linux) +-- Kilde: CpuCollector.HostPlatformSql +-- Kode: src/SQLInsight.Collectors/Performance/CpuCollector.cs : CpuCollector.HostPlatformSql +SELECT host_platform FROM sys.dm_os_host_info +GO diff --git a/SQL Scripts/_collectors/CustomQueryCollector.sql b/SQL Scripts/_collectors/CustomQueryCollector.sql new file mode 100644 index 0000000..dd8961f --- /dev/null +++ b/SQL Scripts/_collectors/CustomQueryCollector.sql @@ -0,0 +1,40 @@ +-- =================================================================================================================== +-- Collector: CustomQueryCollector ("custom-rules") +-- =================================================================================================================== +-- Hvad: Community-reglernes (rules/community/*.json) og lokale reglers forespørgsler: én gang i master +-- (server-regler) eller i hver læsbar brugerdatabase (database-regler). +-- Fil: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:custom-rules) +-- Versioner: SQL Server 2005 og nyere (pr. regel: minimumVersion) +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- 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: COM-AX-001, COM-AX-002, COM-AX-003, COM-AX-004, COM-AX-005, COM-AX-006, COM-AX-007, COM-FT-001, +-- COM-FT-002, COM-FT-003, COM-FT-004, COM-FT-005, COM-DB-001, COM-SEC-001, COM-SEC-002, COM-CFG-001 +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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: Husk databasen, der skal skiftes tilbage til +-- Kilde: CustomQueryCollector.CurrentDatabaseSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql +SELECT DB_NAME() +GO + +-- Variant: Databaserne, database-regler køres i +-- Kilde: CustomQueryCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.DatabasesSql +SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name +GO + +-- Variant: Én regel: skift database (master for server-regler, ellers hver brugerdatabase) og kør reglens forespørgsel +-- Kilde: CustomQueryCollector.BuildRuleSql +-- Kode: src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs : CustomQueryCollector.BuildRuleSql(database, query) +USE [master]; +-- (reglens forespørgsel: se den enkelte COM-*.sql) +GO diff --git a/SQL Scripts/_collectors/DatabaseCollector.sql b/SQL Scripts/_collectors/DatabaseCollector.sql new file mode 100644 index 0000000..19c3d86 --- /dev/null +++ b/SQL Scripts/_collectors/DatabaseCollector.sql @@ -0,0 +1,190 @@ +-- =================================================================================================================== +-- Collector: DatabaseCollector ("databases") +-- =================================================================================================================== +-- Hvad: Databaseindstillinger og -tilstand, backup-historik (msdb), seneste fejlfri CHECKDB, VLF'er, Query +-- Store-tilstand og filer. +-- Fil: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:databases) +-- Versioner: Alle (SQL Server 2000 og nyere); CHECKDB og VLF'er fra 2016 SP2; Query Store fra 2016 +-- +-- 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]; +-- USE msdb; GRANT SELECT ON dbo.backupset TO [agent]; +-- +-- Bruges af: SQLI-DB-001, SQLI-DB-002, SQLI-DB-003, SQLI-DB-004, SQLI-DB-005, SQLI-DB-006, SQLI-DB-007, +-- SQLI-DB-008, SQLI-DB-009, SQLI-DB-011, SQLI-DB-012, SQLI-DB-013, SQLI-DB-014, SQLI-DB-015, +-- SQLI-STAT-002 +-- Read-only: Ja: kun SELECT samt 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 SP2 og nyere (med LastGoodCheckDbTime og availability groups) +-- Kilde: DatabaseCollector.BuildSql2005(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(DATABASEPROPERTYEX(d.name, 'LastGoodCheckDbTime') AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2012 – 2016 SP1 +-- Kilde: DatabaseCollector.BuildSql2005(2012) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + OR EXISTS (SELECT 1 FROM sys.dm_hadr_database_replica_states rs + JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = rs.replica_id + WHERE rs.database_id = d.database_id AND rs.is_local = 1 AND ars.role = 2) + THEN 1 ELSE 0 END, + CASE WHEN d.replica_id IS NOT NULL THEN 1 ELSE 0 END, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 – 2008 R2 +-- Kilde: DatabaseCollector.BuildSql2005(2005) +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2005(instance, capabilities) +SELECT d.name, d.database_id, d.state_desc, d.recovery_model_desc, d.compatibility_level, d.page_verify_option_desc, + d.is_auto_close_on, d.is_auto_shrink_on, d.is_read_only, d.is_trustworthy_on, d.create_date, + CASE WHEN EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) + + THEN 1 ELSE 0 END, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.create_date) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.create_date) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.create_date) THEN b.last_log END, + CAST(NULL AS datetime), + d.is_auto_create_stats_on, d.is_auto_update_stats_on +FROM sys.databases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.BuildSql2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildSql2000() +SELECT d.name, d.dbid, + CAST(DATABASEPROPERTYEX(d.name, 'Status') AS nvarchar(60)), + CAST(DATABASEPROPERTYEX(d.name, 'Recovery') AS nvarchar(60)), + d.cmptlevel, + CASE WHEN DATABASEPROPERTYEX(d.name, 'IsTornPageDetectionEnabled') = 1 THEN N'TORN_PAGE_DETECTION' ELSE N'NONE' END, + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoClose') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoShrink') AS int), + CASE WHEN DATABASEPROPERTYEX(d.name, 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END, + 0, + d.crdate, + 0, + 0, + CASE WHEN b.last_full >= DATEADD(second, -1, d.crdate) THEN b.last_full END, + CASE WHEN b.last_diff >= DATEADD(second, -1, d.crdate) THEN b.last_diff END, + CASE WHEN b.last_log >= DATEADD(second, -1, d.crdate) THEN b.last_log END, + CAST(NULL AS datetime), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoCreateStatistics') AS int), + CAST(DATABASEPROPERTYEX(d.name, 'IsAutoUpdateStatistics') AS int) +FROM master.dbo.sysdatabases d +LEFT JOIN ( + SELECT database_name, + MAX(CASE WHEN type = 'D' THEN backup_finish_date END) AS last_full, + MAX(CASE WHEN type = 'I' THEN backup_finish_date END) AS last_diff, + MAX(CASE WHEN type = 'L' THEN backup_finish_date END) AS last_log, + MIN(backup_finish_date) AS first_backup + FROM msdb.dbo.backupset + GROUP BY database_name +) b ON b.database_name = d.name +WHERE d.name <> N'tempdb' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: DatabaseCollector.Files2005 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2005 +SELECT database_id, name, type_desc, physical_name, + CAST(size AS bigint) / 128, + CASE WHEN is_percent_growth = 1 THEN growth ELSE CAST(growth AS bigint) / 128 END, + is_percent_growth, + CASE WHEN max_size = -1 THEN -1 ELSE CAST(max_size AS bigint) / 128 END +FROM sys.master_files +WHERE type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: DatabaseCollector.Files2000 +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.Files2000 +SELECT dbid, name, CASE WHEN groupid = 0 THEN N'LOG' ELSE N'ROWS' END, filename, + CAST(size AS bigint) / 128, + CASE WHEN status & 0x100000 <> 0 THEN growth ELSE CAST(growth AS bigint) / 128 END, + CASE WHEN status & 0x100000 <> 0 THEN 1 ELSE 0 END, + CASE WHEN maxsize = -1 THEN -1 ELSE CAST(maxsize AS bigint) / 128 END +FROM master.dbo.sysaltfiles +GO + +-- Variant: Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid) +-- Kilde: Db.UtcOffsetSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.UtcOffsetSql +SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE()) +GO + +-- Variant: SQL Server 2016 SP2 og nyere (sys.dm_db_log_info) +-- Kilde: DatabaseCollector.VlfSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.VlfSql +SELECT d.database_id, COUNT(*) +FROM sys.databases d +CROSS APPLY sys.dm_db_log_info(d.database_id) li +WHERE d.state = 0 +GROUP BY d.database_id +GO + +-- Variant: SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for msdb) +-- Kilde: DatabaseCollector.BuildQueryStoreSql +-- Kode: src/SQLInsight.Collectors/Databases/DatabaseCollector.cs : DatabaseCollector.BuildQueryStoreSql(database) +SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb +FROM [msdb].sys.database_query_store_options +GO diff --git a/SQL Scripts/_collectors/DeadlockCollector.sql b/SQL Scripts/_collectors/DeadlockCollector.sql new file mode 100644 index 0000000..0550628 --- /dev/null +++ b/SQL Scripts/_collectors/DeadlockCollector.sql @@ -0,0 +1,44 @@ +-- =================================================================================================================== +-- Collector: DeadlockCollector ("deadlocks") +-- =================================================================================================================== +-- Hvad: Nye deadlock-grafer (xml_deadlock_report) fra den indbyggede system_health-session. +-- Fil: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:deadlocks) +-- Versioner: SQL Server 2008 og nyere (filer fra 2012, ellers ring buffer) +-- +-- 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+ +-- +-- Bruges af: SQLI-DLK-001 +-- Read-only: IKKE STRENGT READ-ONLY. DeadlockCollector.BuildFileSql (SQL Server 2012+) opretter en midlertidig +-- tabel i tempdb (SELECT ... INTO #system_health) og sletter den igen (DROP TABLE #system_health) i +-- samme batch. Den er privat for sessionen; ingen brugerdata, indstillinger eller permanente objekter +-- ændres, men det er en skrivning i tempdb. Ring buffer-varianten (2008/2008 R2) er read-only. Øvrigt: +-- kun SELECT samt SET-sessionsindstillinger. +-- 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 2012 og nyere (system_health-filer; første læsning) +-- Kilde: DeadlockCollector.BuildFileSql +-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null) +SELECT object_name, file_name, file_offset, + CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data +INTO #system_health +FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL); + +SELECT event_data FROM #system_health WHERE event_data IS NOT NULL; +SELECT TOP (1) file_name, file_offset FROM #system_health ORDER BY file_name DESC, file_offset DESC; +DROP TABLE #system_health; +GO + +-- Variant: SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses +-- Kilde: DeadlockCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.RingBufferSql +SELECT CAST(t.target_data AS nvarchar(max)) +FROM sys.dm_xe_session_targets t +JOIN sys.dm_xe_sessions s ON s.address = t.event_session_address +WHERE s.name = N'system_health' AND t.target_name = N'ring_buffer' +GO diff --git a/SQL Scripts/_collectors/ErrorLogCollector.sql b/SQL Scripts/_collectors/ErrorLogCollector.sql new file mode 100644 index 0000000..8511e48 --- /dev/null +++ b/SQL Scripts/_collectors/ErrorLogCollector.sql @@ -0,0 +1,82 @@ +-- =================================================================================================================== +-- Collector: ErrorLogCollector ("error-log") +-- =================================================================================================================== +-- Hvad: SQL Server-fejlloggen siden forrige måling fra den bedste kilde, agentens rettigheder giver: 1) +-- fejlloggen (xp_readerrorlog, kræver securityadmin), 2) default trace (kræver ALTER TRACE), 3) +-- exception- og connectivity ring buffer (kun fejlnumre). +-- Fil: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:error-log) +-- Versioner: SQL Server 2005 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+ +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- Valgfrit: ALTER SERVER ROLE [securityadmin] ADD MEMBER [agent]; -- xp_readerrorlog; gives IKKE som standard +-- +-- Bruges af: SQLI-LOG-001, SQLI-LOG-002, SQLI-LOG-003, SQLI-LOG-004, SQLI-LOG-005, SQLI-LOG-006 +-- Read-only: Ja: kun SELECT samt xp_readerrorlog (læser fejlloggen), sys.fn_trace_gettable (læser default trace), +-- 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 2005 og nyere +-- Kilde: ErrorLogCollector.CanReadErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.CanReadErrorLogSql +SELECT CASE WHEN IS_SRVROLEMEMBER(N'securityadmin') = 1 OR IS_SRVROLEMEMBER(N'sysadmin') = 1 THEN 1 ELSE 0 END, + DATEDIFF(minute, GETUTCDATE(), GETDATE()), GETUTCDATE() +GO + +-- Variant: SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin) +-- Kilde: ErrorLogCollector.BuildErrorLogSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal) +-- Agenten indsætter tidspunktet for forrige måling og nu + 1 minut (serverens lokale tid) som literaler, fx +-- '2026-10-02T09:00:00'. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fra nvarchar(19); +DECLARE @til nvarchar(19); +SET @fra = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SET @til = CONVERT(nvarchar(19), DATEADD(minute, 1, GETDATE()), 126); +EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE) +-- Kilde: ErrorLogCollector.BuildDefaultTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal) +-- Agenten indsætter filargumenterne som literaler: første gang og efter rollover mappen + log.trc med DEFAULT (alle +-- rollover-filer), ellers den aktuelle fil med 1 (kun den fil), samt tidspunktet for forrige måling (lokal tid). +-- Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (20, 22) AND StartTime > @siden +GO + +-- Variant: SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE) +-- Kilde: ErrorLogCollector.RingBufferSql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.RingBufferSql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//Exception/Error)[1]', 'int'), rb.x.value('(//Exception/Severity)[1]', 'int') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION') rb +CROSS JOIN sys.dm_os_sys_info i +GO + +-- Variant: SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer +-- Kilde: ErrorLogCollector.ConnectivitySql +-- Kode: src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs : ErrorLogCollector.ConnectivitySql +SELECT DATEADD(millisecond, -CAST((i.ms_ticks - rb.timestamp) % 1000 AS int), DATEADD(second, -CAST((i.ms_ticks - rb.timestamp) / 1000 AS int), GETUTCDATE())), + rb.x.value('(//SniConsumerError)[1]', 'int'), rb.x.value('(//State)[1]', 'int'), rb.x.value('(//RemoteHost)[1]', 'nvarchar(128)') +FROM (SELECT timestamp, CAST(record AS xml) AS x FROM sys.dm_os_ring_buffers + WHERE ring_buffer_type = N'RING_BUFFER_CONNECTIVITY' AND record LIKE N'%18456%') rb +CROSS JOIN sys.dm_os_sys_info i +GO diff --git a/SQL Scripts/_collectors/FileIoCollector.sql b/SQL Scripts/_collectors/FileIoCollector.sql new file mode 100644 index 0000000..d923e53 --- /dev/null +++ b/SQL Scripts/_collectors/FileIoCollector.sql @@ -0,0 +1,57 @@ +-- =================================================================================================================== +-- Collector: FileIoCollector ("file-io") +-- =================================================================================================================== +-- Hvad: Kumulativ I/O og I/O-ventetid pr. data- og logfil, med drev/mount point. Reglerne bruger forskellen +-- mellem to målinger. +-- Fil: src/SQLInsight.Collectors/Performance/FileIoCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:file-io) +-- Versioner: SQL Server 2005 og nyere; drev fra 2008 R2 SP1 +-- +-- 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 +-- +-- Bruges af: SQLI-IO-001, SQLI-IO-002 +-- Read-only: Ja: kun SELECT samt 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 2008 og nyere: starttidspunkt +-- Kilde: FileIoCollector.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: FileIoCollector.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 R2 SP1 og nyere (med drev/mount point) +-- Kilde: FileIoCollector.SqlWithVolumes +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.SqlWithVolumes +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + vs.volume_mount_point +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +OUTER APPLY sys.dm_os_volume_stats(vfs.database_id, vfs.file_id) vs +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO + +-- Variant: SQL Server 2005 – 2008 R2 RTM +-- Kilde: FileIoCollector.Sql +-- Kode: src/SQLInsight.Collectors/Performance/FileIoCollector.cs : FileIoCollector.Sql +SELECT DB_NAME(vfs.database_id), vfs.file_id, mf.type_desc, mf.physical_name, + vfs.num_of_reads, vfs.num_of_bytes_read, vfs.io_stall_read_ms, + vfs.num_of_writes, vfs.num_of_bytes_written, vfs.io_stall_write_ms, + CAST(NULL AS nvarchar(260)) +FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs +JOIN sys.master_files mf ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_id +WHERE mf.type_desc IN (N'ROWS', N'LOG') +GO diff --git a/SQL Scripts/_collectors/IndexCollector.sql b/SQL Scripts/_collectors/IndexCollector.sql new file mode 100644 index 0000000..ff60674 --- /dev/null +++ b/SQL Scripts/_collectors/IndexCollector.sql @@ -0,0 +1,123 @@ +-- =================================================================================================================== +-- Collector: IndexCollector ("indexes") +-- =================================================================================================================== +-- Hvad: Index-inventar med brug, manglende index-forslag og statistikker for hver læsbar brugerdatabase. Kun +-- metadata og DMV'er; ingen tabeldata, ingen scanning. +-- Fil: src/SQLInsight.Collectors/Databases/IndexCollector.cs +-- Interval: 6 timer som standard (SQLInsight:CollectorIntervals:indexes) +-- Versioner: SQL Server 2005 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-IDX-001, SQLI-IDX-002, SQLI-IDX-003, SQLI-IDX-004, SQLI-STAT-001 +-- Read-only: Ja: kun SELECT samt 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 2008 og nyere: starttidspunkt +-- Kilde: IndexCollector.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: IndexCollector.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 2005 og nyere +-- Kilde: IndexCollector.DatabasesSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.DatabasesSql +SELECT d.name FROM sys.databases d +WHERE d.state = 0 AND d.database_id > 4 AND HAS_DBACCESS(d.name) = 1 + AND NOT EXISTS (SELECT 1 FROM sys.database_mirroring m WHERE m.database_id = d.database_id AND m.mirroring_role = 2) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: IndexCollector.MissingSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.MissingSql +SELECT DB_NAME(d.database_id), d.statement, d.equality_columns, d.inequality_columns, d.included_columns, + gs.user_seeks, gs.user_scans, gs.avg_total_user_cost, gs.avg_user_impact, gs.last_user_seek +FROM sys.dm_db_missing_index_details d +JOIN sys.dm_db_missing_index_groups g ON g.index_handle = d.index_handle +JOIN sys.dm_db_missing_index_group_stats gs ON gs.group_handle = g.index_group_handle +GO + +-- Variant: SQL Server 2008 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2008+) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: true) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + i.filter_definition, + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildIndexSql(2005) +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildIndexSql(database, filteredIndexes: false) +SELECT s.name, o.name, i.index_id, i.name, i.type_desc, i.is_unique, i.is_primary_key, i.is_unique_constraint, i.is_disabled, + CAST(NULL AS nvarchar(max)), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 0 + AND ic.key_ordinal > 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + STUFF((SELECT NCHAR(57344) + c.name + CASE WHEN ic.is_descending_key = 1 THEN N' DESC' ELSE N'' END + FROM [msdb].sys.index_columns ic JOIN [msdb].sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.is_included_column = 1 + + ORDER BY c.name + FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, N''), + ps.row_count, ps.used_pages, + us.user_seeks, us.user_scans, us.user_lookups, us.user_updates, + fw.forwarded +FROM [msdb].sys.indexes i +JOIN [msdb].sys.objects o ON o.object_id = i.object_id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +OUTER APPLY (SELECT SUM(p.row_count) AS row_count, SUM(p.used_page_count) AS used_pages + FROM [msdb].sys.dm_db_partition_stats p WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps +LEFT JOIN sys.dm_db_index_usage_stats us ON us.database_id = DB_ID(N'msdb') AND us.object_id = i.object_id AND us.index_id = i.index_id +OUTER APPLY (SELECT SUM(os.forwarded_fetch_count) AS forwarded + FROM sys.dm_db_index_operational_stats(DB_ID(N'msdb'), i.object_id, 0, NULL) os WHERE i.index_id = 0) fw; +GO + +-- Variant: SQL Server 2005 og nyere: én gang pr. database (vist for msdb) +-- Kilde: IndexCollector.BuildStatisticsSql +-- Kode: src/SQLInsight.Collectors/Databases/IndexCollector.cs : IndexCollector.BuildStatisticsSql(database) +SELECT s.name, o.name, si.name, STATS_DATE(si.id, si.indid), + COALESCE((SELECT TOP 1 t.rowcnt FROM [msdb].sys.sysindexes t WHERE t.id = si.id AND t.indid IN (0, 1)), si.rowcnt), si.rowmodctr +FROM [msdb].sys.sysindexes si +JOIN [msdb].sys.objects o ON o.object_id = si.id AND o.type = 'U' AND o.is_ms_shipped = 0 +JOIN [msdb].sys.schemas s ON s.schema_id = o.schema_id +WHERE si.indid > 0 AND si.indid < 255; +GO diff --git a/SQL Scripts/_collectors/InstanceConfigurationCollector.sql b/SQL Scripts/_collectors/InstanceConfigurationCollector.sql new file mode 100644 index 0000000..f5983a6 --- /dev/null +++ b/SQL Scripts/_collectors/InstanceConfigurationCollector.sql @@ -0,0 +1,82 @@ +-- =================================================================================================================== +-- Collector: InstanceConfigurationCollector ("instance-config") +-- =================================================================================================================== +-- Hvad: Instansens opsætning til konfigurationssiden og ændringsloggen: egenskaber, tempdb-filer, tjenester og +-- konti, Instant File Initialization, memory model, sp_configure og trace flags. +-- Fil: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:instance-config) +-- Versioner: Alle; tjenester og IFI fra SQL Server 2008 R2 SP1 +-- +-- 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 +-- +-- Bruges af: SQLI-CFG-013, SQLI-SVC-001 +-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), 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 2005 og nyere (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2005 +SELECT name, CAST(value AS bigint), CAST(value_in_use AS bigint) FROM sys.configurations +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) (samme forespørgsel som ConfigurationCollector) +-- Kilde: ConfigurationCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ConfigurationCollector.cs : ConfigurationCollector.Sql2000 +SELECT v.name, CAST(c.value AS bigint), CAST(r.value AS bigint) +FROM master.dbo.spt_values v +JOIN master.dbo.sysconfigures c ON c.config = v.number +JOIN master.dbo.syscurconfigs r ON r.config = v.number +WHERE v.type = 'C' +GO + +-- Variant: Alle versioner (SQL Server 2000 og nyere) (samme forespørgsel som TraceFlagCollector) +-- Kilde: TraceFlagCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql +DBCC TRACESTATUS(-1) WITH NO_INFOMSGS +GO + +-- Variant: Alle versioner +-- Kilde: InstanceConfigurationCollector.PropertiesSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.PropertiesSql +SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), CAST(SERVERPROPERTY('Collation') AS nvarchar(128)), + CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(260)), CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(260)) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: InstanceConfigurationCollector.TempdbSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.TempdbSql +SELECT name, type_desc, CAST(size AS bigint) / 128, growth, is_percent_growth, max_size +FROM sys.master_files WHERE database_id = 2 ORDER BY file_id +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere +-- Kilde: InstanceConfigurationCollector.ColumnsSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.ColumnsSql +SELECT COL_LENGTH('sys.dm_server_services', 'instant_file_initialization_enabled'), COL_LENGTH('sys.dm_os_sys_info', 'sql_memory_model_desc') +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(med IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(true) +SELECT servicename, service_account, startup_type_desc, status_desc, instant_file_initialization_enabled FROM sys.dm_server_services +GO + +-- Variant: SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled +-- Kilde: InstanceConfigurationCollector.BuildServicesSql(uden IFI) +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.BuildServicesSql(false) +SELECT servicename, service_account, startup_type_desc, status_desc, CAST(NULL AS nvarchar(1)) FROM sys.dm_server_services +GO + +-- Variant: Builds med kolonnen sql_memory_model_desc (se ColumnsSql) +-- Kilde: InstanceConfigurationCollector.MemoryModelSql +-- Kode: src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs : InstanceConfigurationCollector.MemoryModelSql +SELECT sql_memory_model_desc FROM sys.dm_os_sys_info +GO diff --git a/SQL Scripts/_collectors/InstanceProbe.sql b/SQL Scripts/_collectors/InstanceProbe.sql new file mode 100644 index 0000000..6d483f9 --- /dev/null +++ b/SQL Scripts/_collectors/InstanceProbe.sql @@ -0,0 +1,37 @@ +-- =================================================================================================================== +-- Collector: InstanceProbe +-- =================================================================================================================== +-- Hvad: Registrering af instansen: version, udgave, cluster og Always On. Køres, når agenten forbinder, og +-- bestemmer hvilke varianter og regler der bruges. +-- Fil: src/SQLInsight.Connectivity/InstanceProbe.cs +-- Interval: én gang, når agenten forbinder til instansen +-- Versioner: Alle (SQL Server 2000 og nyere) +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Bruges af: SQLI-VER-001, SQLI-VER-002, SQLI-CFG-009, SQLI-CFG-012, SQLI-DB-006, SQLI-JOB-002 +-- Read-only: Ja: kun SELECT samt 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: 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 + +-- Variant: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: InstanceProbe.Query +-- Kode: src/SQLInsight.Connectivity/InstanceProbe.cs : InstanceProbe.Query +SELECT + CAST(SERVERPROPERTY('ServerName') AS nvarchar(256)), + CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)), + CAST(SERVERPROPERTY('Edition') AS nvarchar(128)), + CAST(SERVERPROPERTY('EngineEdition') AS int), + CAST(SERVERPROPERTY('IsClustered') AS int), + CAST(SERVERPROPERTY('IsHadrEnabled') AS int) +GO diff --git a/SQL Scripts/_collectors/LongOperationCollector.sql b/SQL Scripts/_collectors/LongOperationCollector.sql new file mode 100644 index 0000000..5390fae --- /dev/null +++ b/SQL Scripts/_collectors/LongOperationCollector.sql @@ -0,0 +1,32 @@ +-- =================================================================================================================== +-- Collector: LongOperationCollector ("long-operations") +-- =================================================================================================================== +-- Hvad: Operationer, SQL Server rapporterer fremdrift for (BACKUP, RESTORE, DBCC, ALTER INDEX REORGANIZE, +-- SHRINK, ROLLBACK ...). +-- Fil: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs +-- Interval: 1 min som standard (SQLInsight:CollectorIntervals:long-operations) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-OPS-001 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere +-- Kilde: LongOperationCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/LongOperationCollector.cs : LongOperationCollector.Sql +SELECT r.session_id, r.command, DB_NAME(r.database_id), s.login_name, + DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), r.start_time), + CAST(r.percent_complete AS float), + CASE WHEN r.estimated_completion_time > 0 THEN DATEADD(second, CAST(r.estimated_completion_time / 1000 AS int), GETUTCDATE()) END, + GETUTCDATE() +FROM sys.dm_exec_requests r +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +WHERE r.percent_complete > 0 AND r.session_id <> @@SPID +GO diff --git a/SQL Scripts/_collectors/PerformanceCounterCollector.sql b/SQL Scripts/_collectors/PerformanceCounterCollector.sql new file mode 100644 index 0000000..7f27147 --- /dev/null +++ b/SQL Scripts/_collectors/PerformanceCounterCollector.sql @@ -0,0 +1,60 @@ +-- =================================================================================================================== +-- Collector: PerformanceCounterCollector ("performance-counters") +-- =================================================================================================================== +-- Hvad: Udvalgte performance counters: memory grants, page life expectancy, forbindelser, blokerede processer, +-- batch requests og kompileringer. +-- Fil: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:performance-counters) +-- Versioner: Alle (SQL Server 2000 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Bruges af: SQLI-MEM-001, SQLI-ANOM-001 +-- Read-only: Ja: kun SELECT samt 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 2008 og nyere: starttidspunkt +-- Kilde: PerformanceCounterCollector.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: PerformanceCounterCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: PerformanceCounterCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PerformanceCounterCollector.BuildSql(dmv) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: true) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM sys.dm_os_performance_counters +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: PerformanceCounterCollector.BuildSql(sysperfinfo) +-- Kode: src/SQLInsight.Collectors/Performance/PerformanceCounterCollector.cs : PerformanceCounterCollector.BuildSql(dmv: false) +SELECT RTRIM(object_name), RTRIM(counter_name), CAST(cntr_value AS bigint) +FROM master.dbo.sysperfinfo +WHERE (object_name LIKE N'%Memory Manager%' AND counter_name IN (N'Memory Grants Pending', N'Memory Grants Outstanding')) + OR (object_name LIKE N'%Buffer Manager%' AND counter_name = N'Page life expectancy') + OR (object_name LIKE N'%General Statistics%' AND counter_name IN (N'User Connections', N'Processes blocked')) + OR (object_name LIKE N'%SQL Statistics%' AND counter_name IN (N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec')) +GO diff --git a/SQL Scripts/_collectors/PlanAnalysisCollector.sql b/SQL Scripts/_collectors/PlanAnalysisCollector.sql new file mode 100644 index 0000000..1de8c61 --- /dev/null +++ b/SQL Scripts/_collectors/PlanAnalysisCollector.sql @@ -0,0 +1,91 @@ +-- =================================================================================================================== +-- Collector: PlanAnalysisCollector ("plan-analysis") +-- =================================================================================================================== +-- Hvad: De 50 sætninger med mest CPU det seneste døgn og deres cachede planer (som tekst), som analyseres i C# +-- (ShowplanAnalyzer). +-- Fil: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs +-- Interval: 30 min som standard (SQLInsight:CollectorIntervals:plan-analysis) +-- Versioner: SQL Server 2005 SP2 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+ +-- +-- Bruges af: SQLI-PLAN-001, SQLI-PLAN-002, SQLI-PLAN-003, SQLI-PLAN-004, SQLI-PLAN-005 +-- Read-only: Ja: kun SELECT samt 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 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner) +-- Kilde: PlanAnalysisCollector.BuildSql(2016 SP2+) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + qs.total_spills, qs.total_grant_kb, qs.total_used_grant_kb, + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2008 – 2016 SP1 og 2017 før CU3 +-- Kilde: PlanAnalysisCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT CONVERT(varchar(20), qs.query_hash, 1), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO + +-- Variant: SQL Server 2005 SP2 og nyere 2005-builds +-- Kilde: PlanAnalysisCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Performance/PlanAnalysisCollector.cs : PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false) +WITH top_statements AS ( + SELECT TOP (50) qs.* + FROM sys.dm_exec_query_stats qs + WHERE qs.last_execution_time >= DATEADD(hour, -24, GETDATE()) + ORDER BY qs.total_worker_time DESC +) +SELECT sys.fn_varbintohexstr(qs.sql_handle) + ':' + CAST(qs.statement_start_offset AS varchar(12)) + ':' + CAST(qs.statement_end_offset AS varchar(12)), + DB_NAME(CONVERT(int, pa.value)), + LEFT(SUBSTRING(t.text, qs.statement_start_offset / 2 + 1, + (CASE WHEN qs.statement_end_offset = -1 THEN DATALENGTH(t.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2 + 1), 4000), + qs.execution_count, qs.total_worker_time, qs.total_elapsed_time, qs.total_logical_reads, + CAST(NULL AS bigint), CAST(NULL AS bigint), CAST(NULL AS bigint), + qs.last_execution_time, + tp.query_plan +FROM top_statements qs +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t +OUTER APPLY sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) tp +OUTER APPLY (SELECT value FROM sys.dm_exec_plan_attributes(qs.plan_handle) WHERE attribute = N'dbid') pa +ORDER BY qs.total_worker_time DESC; +GO diff --git a/SQL Scripts/_collectors/PlanCacheCollector.sql b/SQL Scripts/_collectors/PlanCacheCollector.sql new file mode 100644 index 0000000..b094418 --- /dev/null +++ b/SQL Scripts/_collectors/PlanCacheCollector.sql @@ -0,0 +1,29 @@ +-- =================================================================================================================== +-- Collector: PlanCacheCollector ("plan-cache") +-- =================================================================================================================== +-- Hvad: Plan cache: antal og størrelse af planer og af ad hoc-planer brugt én gang. +-- Fil: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:plan-cache) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-CFG-005 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere +-- Kilde: PlanCacheCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/PlanCacheCollector.cs : PlanCacheCollector.Sql +SELECT + COUNT_BIG(*), + ISNULL(SUM(CAST(size_in_bytes AS bigint)), 0) / 1048576.0, + ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN 1 ELSE 0 END), 0), + ISNULL(SUM(CASE WHEN objtype = N'Adhoc' AND usecounts = 1 THEN CAST(size_in_bytes AS bigint) ELSE 0 END), 0) / 1048576.0 +FROM sys.dm_exec_cached_plans +GO diff --git a/SQL Scripts/_collectors/PrivilegeCollector.sql b/SQL Scripts/_collectors/PrivilegeCollector.sql new file mode 100644 index 0000000..2da757f --- /dev/null +++ b/SQL Scripts/_collectors/PrivilegeCollector.sql @@ -0,0 +1,67 @@ +-- =================================================================================================================== +-- Collector: PrivilegeCollector ("privileges") +-- =================================================================================================================== +-- Hvad: Medlemmer af de faste serverroller og indehavere af CONTROL SERVER, SQL-logins' password-politik og - +-- med ALTER TRACE - hvem der ændrede rettighederne (default trace). +-- Fil: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:privileges) +-- Versioner: SQL Server 2005 og nyere +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Valgfrit: GRANT ALTER TRACE TO [agent]; -- default trace (udkommenteret i grant-least-privilege.sql; uden den bruges næste kilde) +-- +-- Bruges af: SQLI-SEC-001, SQLI-SEC-002 +-- Read-only: Ja: kun SELECT samt sys.fn_trace_gettable (læser default trace), 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 2005 og nyere +-- Kilde: PrivilegeCollector.MembersSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.MembersSql +SELECT p.name, p.type, r.name, p.is_disabled +FROM sys.server_role_members m +JOIN sys.server_principals r ON r.principal_id = m.role_principal_id +JOIN sys.server_principals p ON p.principal_id = m.member_principal_id +WHERE r.name IN (N'sysadmin', N'securityadmin', N'serveradmin', N'setupadmin', N'processadmin', N'diskadmin', N'dbcreator', N'bulkadmin') +UNION ALL +SELECT p.name, p.type, N'CONTROL SERVER', p.is_disabled +FROM sys.server_permissions sp +JOIN sys.server_principals p ON p.principal_id = sp.grantee_principal_id +WHERE sp.permission_name = N'CONTROL SERVER' AND sp.state IN ('G', 'W') +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.SqlLoginsSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.SqlLoginsSql +SELECT name, is_disabled, is_policy_checked, is_expiration_checked, CASE WHEN principal_id = 1 THEN 1 ELSE 0 END +FROM sys.sql_logins WHERE name NOT LIKE N'##%' +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: PrivilegeCollector.DefaultTraceEnabledSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTraceEnabledSql +SELECT CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'default trace enabled' +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.DefaultTracePathSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.DefaultTracePathSql +SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 +GO + +-- Variant: SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE) +-- Kilde: PrivilegeCollector.BuildTraceSql +-- Kode: src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs : PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal) +-- Agenten indsætter default trace-filens sti (mappen + log.trc, så alle rollover-filer læses) og serverens lokale tid +-- for en time før forrige måling som literaler. Her er de variabler, så scriptet kan køres i SSMS. +DECLARE @fil nvarchar(260); +DECLARE @siden nvarchar(19); +SELECT @fil = LEFT(path, LEN(path) - CHARINDEX(N'\', REVERSE(path)) + 1) + N'log.trc' FROM sys.traces WHERE is_default = 1; +SET @siden = CONVERT(nvarchar(19), DATEADD(hour, -1, GETDATE()), 126); +SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName +FROM sys.fn_trace_gettable(@fil, DEFAULT) +WHERE EventClass IN (104, 105, 108) AND StartTime >= @siden +GO diff --git a/SQL Scripts/_collectors/QueryStatsCollector.sql b/SQL Scripts/_collectors/QueryStatsCollector.sql new file mode 100644 index 0000000..8131e55 --- /dev/null +++ b/SQL Scripts/_collectors/QueryStatsCollector.sql @@ -0,0 +1,94 @@ +-- =================================================================================================================== +-- Collector: QueryStatsCollector ("query-stats") +-- =================================================================================================================== +-- Hvad: 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. +-- Fil: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:query-stats) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-CPU-002 +-- Read-only: Ja: kun SELECT samt 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 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 diff --git a/SQL Scripts/_collectors/QueryStoreCollector.sql b/SQL Scripts/_collectors/QueryStoreCollector.sql new file mode 100644 index 0000000..9622475 --- /dev/null +++ b/SQL Scripts/_collectors/QueryStoreCollector.sql @@ -0,0 +1,78 @@ +-- =================================================================================================================== +-- 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 +-- 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 diff --git a/SQL Scripts/_collectors/ReplicationCollector.sql b/SQL Scripts/_collectors/ReplicationCollector.sql new file mode 100644 index 0000000..904525d --- /dev/null +++ b/SQL Scripts/_collectors/ReplicationCollector.sql @@ -0,0 +1,53 @@ +-- =================================================================================================================== +-- Collector: ReplicationCollector ("replication") +-- =================================================================================================================== +-- Hvad: På en distributør: hver distributionsagents seneste status, forsinkelse og besked samt +-- replikeringsfejl det seneste døgn. +-- Fil: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:replication) +-- Versioner: SQL Server 2005 og nyere +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- GRANT VIEW ANY DEFINITION TO [agent]; -- metadata; giver IKKE adgang til data +-- Kun på en distributør, valgfrit (gives ikke af grant-least-privilege.sql): +-- USE distribution; CREATE USER [agent] FOR LOGIN [agent]; +-- GRANT SELECT ON dbo.MSdistribution_agents TO [agent]; GRANT SELECT ON dbo.MSdistribution_history TO [agent]; GRANT SELECT ON dbo.MSrepl_errors TO [agent]; +-- +-- Bruges af: SQLI-REPL-001, SQLI-REPL-002 +-- Read-only: Ja: kun SELECT samt USE (skifter sessionens database), 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 2005 og nyere +-- Kilde: ReplicationCollector.DistributionDatabaseSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.DistributionDatabaseSql +SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0 +GO + +-- Variant: Kun på en distributør: husk databasen, der skal skiftes tilbage til +-- Kilde: ReplicationCollector.CurrentDatabaseSql +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.CurrentDatabaseSql +SELECT DB_NAME() +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.AgentsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.AgentsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency, + CAST(h.comments AS nvarchar(500)) +FROM dbo.MSdistribution_agents a +OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x + WHERE x.agent_id = a.id ORDER BY x.timestamp DESC) h +GO + +-- Variant: Kun på en distributør, i distributionsdatabasen +-- Kilde: ReplicationCollector.ErrorsSql +-- Kode: src/SQLInsight.Collectors/Instance/ReplicationCollector.cs : ReplicationCollector.ErrorsSql +-- Agenten sender "USE [];" foran (navnet fra DistributionDatabaseSql). Ret navnet, hvis det ikke er distribution. +USE [distribution]; +SELECT COUNT(*) FROM dbo.MSrepl_errors WHERE time > DATEADD(hour, -24, GETDATE()) +GO diff --git a/SQL Scripts/_collectors/ServerCollector.sql b/SQL Scripts/_collectors/ServerCollector.sql new file mode 100644 index 0000000..2418c37 --- /dev/null +++ b/SQL Scripts/_collectors/ServerCollector.sql @@ -0,0 +1,84 @@ +-- =================================================================================================================== +-- Collector: ServerCollector ("server") +-- =================================================================================================================== +-- Hvad: Starttidspunkt, logiske CPU'er, schedulers, NUMA-noder, fysisk hukommelse og virtualisering. +-- Fil: src/SQLInsight.Collectors/Instance/ServerCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:server) +-- Versioner: Alle (SQL Server 2000 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Bruges af: SQLI-CFG-001, SQLI-CFG-002, SQLI-CFG-003, SQLI-CFG-004, SQLI-DB-015 +-- Read-only: Ja: kun SELECT samt xp_msver (læser versionsoplysninger), 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 2012 og nyere +-- Kilde: ServerCollector.BuildSql(2012+) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_kb / 1024, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 R2 SP1 og senere 2008 R2-builds +-- Kilde: ServerCollector.BuildSql(2008 R2 SP1) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + virtual_machine_type_desc +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2008 og 2008 R2 før SP1 +-- Kilde: ServerCollector.BuildSql(2008) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT sqlserver_start_time FROM sys.dm_os_sys_info), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2005 +-- Kilde: ServerCollector.BuildSql(2005) +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.BuildSql(version) +SELECT + (SELECT create_date FROM sys.databases WHERE database_id = 2), + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + physical_memory_in_bytes / 1048576, + CAST(NULL AS nvarchar(60)) +FROM sys.dm_os_sys_info +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: ServerCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse +-- Kilde: ServerCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Instance/ServerCollector.cs : ServerCollector.Sql2000 +EXEC master.dbo.xp_msver +GO diff --git a/SQL Scripts/_collectors/SuspectPageCollector.sql b/SQL Scripts/_collectors/SuspectPageCollector.sql new file mode 100644 index 0000000..ae7838c --- /dev/null +++ b/SQL Scripts/_collectors/SuspectPageCollector.sql @@ -0,0 +1,23 @@ +-- =================================================================================================================== +-- Collector: SuspectPageCollector ("suspect-pages") +-- =================================================================================================================== +-- Hvad: Sider, SQL Server har registreret som korrupte (msdb.dbo.suspect_pages). +-- Fil: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:suspect-pages) +-- Versioner: SQL Server 2005 og nyere +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- USE msdb; GRANT SELECT ON dbo.suspect_pages TO [agent]; +-- +-- Bruges af: SQLI-DB-010 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere +-- Kilde: SuspectPageCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/SuspectPageCollector.cs : SuspectPageCollector.Sql +SELECT ISNULL(DB_NAME(database_id), CAST(database_id AS nvarchar(20))), file_id, page_id, event_type, error_count, last_update_date +FROM msdb.dbo.suspect_pages +GO diff --git a/SQL Scripts/_collectors/TempdbCollector.sql b/SQL Scripts/_collectors/TempdbCollector.sql new file mode 100644 index 0000000..028fa72 --- /dev/null +++ b/SQL Scripts/_collectors/TempdbCollector.sql @@ -0,0 +1,67 @@ +-- =================================================================================================================== +-- Collector: TempdbCollector ("tempdb") +-- =================================================================================================================== +-- Hvad: tempdb: fri plads, version store, bruger- og interne objekter, de tungeste sessioner, ældste +-- snapshot-transaktion og hvilke databaser der laver rækkeversioner. +-- Fil: src/SQLInsight.Collectors/Instance/TempdbCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:tempdb) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: SQLI-TEMP-001, SQLI-TEMP-002, SQLI-TEMP-003 +-- Read-only: Ja: kun SELECT samt 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 2005 og nyere +-- Kilde: TempdbCollector.SpaceSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SpaceSql +SELECT SUM(CAST(u.unallocated_extent_page_count AS bigint)), SUM(CAST(u.version_store_reserved_page_count AS bigint)), + SUM(CAST(u.user_object_reserved_page_count AS bigint)), SUM(CAST(u.internal_object_reserved_page_count AS bigint)), + (SELECT SUM(CAST(f.size AS bigint)) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CASE WHEN f.growth > 0 AND (f.max_size = -1 OR f.size < f.max_size) THEN 1 ELSE 0 END) FROM tempdb.sys.database_files f WHERE f.type = 0), + (SELECT MAX(CAST(p.cntr_value AS bigint)) FROM sys.dm_os_performance_counters p + WHERE p.counter_name = N'Version Store Size (KB)' AND p.object_name LIKE N'%Transactions%') +FROM tempdb.sys.dm_db_file_space_usage u +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.SessionsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.SessionsSql +SELECT TOP 5 u.session_id, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), s.login_time), u.pages, s.login_name, s.program_name, s.host_name, s.status +FROM (SELECT a.session_id, SUM(a.uo_alloc - a.uo_dealloc + a.io_alloc - a.io_dealloc) AS pages + FROM (SELECT session_id, CAST(user_objects_alloc_page_count AS bigint) AS uo_alloc, CAST(user_objects_dealloc_page_count AS bigint) AS uo_dealloc, + CAST(internal_objects_alloc_page_count AS bigint) AS io_alloc, CAST(internal_objects_dealloc_page_count AS bigint) AS io_dealloc + FROM sys.dm_db_session_space_usage + UNION ALL + SELECT session_id, CAST(user_objects_alloc_page_count AS bigint), CAST(user_objects_dealloc_page_count AS bigint), + CAST(internal_objects_alloc_page_count AS bigint), CAST(internal_objects_dealloc_page_count AS bigint) + FROM sys.dm_db_task_space_usage) a + GROUP BY a.session_id) u +JOIN sys.dm_exec_sessions s ON s.session_id = u.session_id +WHERE s.is_user_process = 1 AND u.pages > 0 +ORDER BY u.pages DESC +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: TempdbCollector.OldestSnapshotSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.OldestSnapshotSql +SELECT TOP 1 t.session_id, t.elapsed_time_seconds, s.login_name, s.program_name +FROM sys.dm_tran_active_snapshot_database_transactions t +LEFT JOIN sys.dm_exec_sessions s ON s.session_id = t.session_id +ORDER BY t.elapsed_time_seconds DESC +GO + +-- Variant: SQL Server 2005 og nyere, kun når version store er over halvdelen af grænsen, højst én gang i timen +-- Kilde: TempdbCollector.GeneratorsSql +-- Kode: src/SQLInsight.Collectors/Instance/TempdbCollector.cs : TempdbCollector.GeneratorsSql +SELECT TOP 10 DB_NAME(database_id), SUM(CAST(aggregated_record_length_in_bytes AS bigint)) +FROM sys.dm_tran_top_version_generators +GROUP BY database_id +ORDER BY SUM(CAST(aggregated_record_length_in_bytes AS bigint)) DESC +GO diff --git a/SQL Scripts/_collectors/TraceFlagCollector.sql b/SQL Scripts/_collectors/TraceFlagCollector.sql new file mode 100644 index 0000000..3f47491 --- /dev/null +++ b/SQL Scripts/_collectors/TraceFlagCollector.sql @@ -0,0 +1,23 @@ +-- =================================================================================================================== +-- Collector: TraceFlagCollector ("trace-flags") +-- =================================================================================================================== +-- Hvad: De globalt slåede trace flags. +-- Fil: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs +-- Interval: 15 min som standard (SQLInsight:CollectorIntervals:trace-flags) +-- Versioner: Alle (SQL Server 2000 og nyere) +-- +-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login): +-- Ingen ekstra rettighed: public må læse dette (SERVERPROPERTY, sys.configurations, DBCC TRACESTATUS, xp_msver). +-- +-- Bruges af: SQLI-CFG-012 +-- Read-only: Ja: kun SELECT samt DBCC TRACESTATUS (læser trace flags), 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: Alle versioner (SQL Server 2000 og nyere) +-- Kilde: TraceFlagCollector.Sql +-- Kode: src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs : TraceFlagCollector.Sql +DBCC TRACESTATUS(-1) WITH NO_INFOMSGS +GO diff --git a/SQL Scripts/_collectors/WaitSamplingCollector.sql b/SQL Scripts/_collectors/WaitSamplingCollector.sql new file mode 100644 index 0000000..e1e3891 --- /dev/null +++ b/SQL Scripts/_collectors/WaitSamplingCollector.sql @@ -0,0 +1,46 @@ +-- =================================================================================================================== +-- Collector: WaitSamplingCollector ("wait-sampling") +-- =================================================================================================================== +-- Hvad: Active session sampling: hvad ventende brugerrequests venter på. Bruges af tidsvindue-analysen i +-- web-UI'et, ikke af nogen regel. +-- Fil: src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs +-- Interval: 15 sek som standard (SQLInsight:CollectorIntervals:wait-sampling) +-- Versioner: SQL Server 2005 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+ +-- +-- Bruges af: Ingen regel (kun web-UI'ets analyse). +-- Read-only: Ja: kun SELECT samt 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 2008 og nyere +-- Kilde: WaitSamplingCollector.Sql2008 +-- Kode: src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs : WaitSamplingCollector.Sql2008 +SELECT r.session_id, r.wait_type, r.wait_time, DB_NAME(r.database_id), r.blocking_session_id,CASE WHEN r.query_hash IS NULL OR r.query_hash = 0x0000000000000000 THEN NULL ELSE CONVERT(varchar(20), r.query_hash, 1) END, +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)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st +WHERE s.is_user_process = 1 AND r.session_id <> @@SPID AND r.wait_type IS NOT NULL + AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') +GO + +-- Variant: SQL Server 2005 +-- Kilde: WaitSamplingCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs : WaitSamplingCollector.Sql2005 +SELECT r.session_id, r.wait_type, r.wait_time, DB_NAME(r.database_id), r.blocking_session_id,CASE WHEN r.sql_handle IS NULL THEN NULL + ELSE sys.fn_varbintohexstr(r.sql_handle) + ':' + CAST(r.statement_start_offset AS varchar(12)) + ':' + CAST(r.statement_end_offset AS varchar(12)) END, +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)) +FROM sys.dm_exec_requests r +JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id +OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st +WHERE s.is_user_process = 1 AND r.session_id <> @@SPID AND r.wait_type IS NOT NULL + AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%') +GO diff --git a/SQL Scripts/_collectors/WaitStatsCollector.sql b/SQL Scripts/_collectors/WaitStatsCollector.sql new file mode 100644 index 0000000..6232cb5 --- /dev/null +++ b/SQL Scripts/_collectors/WaitStatsCollector.sql @@ -0,0 +1,54 @@ +-- =================================================================================================================== +-- Collector: WaitStatsCollector ("wait-stats") +-- =================================================================================================================== +-- Hvad: Kumulative ventetider. Reglerne bruger forskellen mellem to målinger; en genstart (nyt starttidspunkt) +-- nulstiller. +-- Fil: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs +-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:wait-stats) +-- Versioner: Alle (SQL Server 2000 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+ +-- SQL Server 2000 har ikke VIEW SERVER STATE; se docs/legacy-versions.md (fx kræver DBCC SQLPERF(WAITSTATS) forhøjede rettigheder). +-- +-- Bruges af: SQLI-WAIT-001, SQLI-WAIT-005, SQLI-WAIT-002, SQLI-WAIT-003, SQLI-WAIT-004, SQLI-WAIT-006, +-- SQLI-WAIT-007, SQLI-MEM-001 +-- Read-only: Ja: kun SELECT samt DBCC SQLPERF(WAITSTATS) (læser ventetider, SQL Server 2000), +-- 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 2008 og nyere: starttidspunkt (nulstilling af kumulative tal) +-- Kilde: WaitStatsCollector.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: WaitStatsCollector.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 2000 (legacy ODBC-sti): starttidspunkt +-- Kilde: WaitStatsCollector.StartTimeSql(2000) +-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version) +SELECT (SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1) +GO + +-- Variant: SQL Server 2005 og nyere +-- Kilde: WaitStatsCollector.Sql2005 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2005 +SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms +FROM sys.dm_os_wait_stats +WHERE wait_time_ms > 0 +GO + +-- Variant: SQL Server 2000 (legacy ODBC-sti) +-- Kilde: WaitStatsCollector.Sql2000 +-- Kode: src/SQLInsight.Collectors/Performance/WaitStatsCollector.cs : WaitStatsCollector.Sql2000 +DBCC SQLPERF(WAITSTATS) WITH NO_INFOMSGS +GO diff --git a/src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs b/src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs index 635b4d3..d41a54e 100644 --- a/src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs +++ b/src/SQLInsight.Collectors/Concurrency/ActivityCollector.cs @@ -60,16 +60,10 @@ public sealed class ActivityCollector(int transactionMinutes = 10, int transacti protected override async Task CollectAsync(CollectorContext context, ActivitySnapshot? previous) { var ct = context.CancellationToken; - var logBytes = (long)transactionLogMegabytes * 1024 * 1024; - var transactionsSql = TransactionsSql.Replace("{notAgent}", NotAgent, StringComparison.Ordinal) - .Replace("{logBytes}", logBytes.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); - var transactions = await Db.QueryAsync(context.Connection, transactionsSql, r => new OpenTransaction( + var transactions = await Db.QueryAsync(context.Connection, BuildTransactionsSql(transactionLogMegabytes), r => new OpenTransaction( r.Int(0), r.Long(1), r.NullableString(2), Utc(r.DateTime(3)), r.Long(4), r.Long(5), r.NullableString(6), r.Int(7), r.NullableString(8), r.NullableString(9), r.NullableString(10), r.NullableString(11), r.NullableString(12), r.NullableString(13)), ct).ConfigureAwait(false); - var thresholdMs = (long)requestMinutes * 60_000; - var sql = LongRequestsSql.Replace("{notAgent}", NotAgent, StringComparison.Ordinal) - .Replace("{thresholdMs}", thresholdMs.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); - var requests = await Db.QueryAsync(context.Connection, sql, r => new LongRunningRequest( + var requests = await Db.QueryAsync(context.Connection, BuildLongRequestsSql(requestMinutes), r => new LongRunningRequest( r.Int(0), Utc(r.DateTime(1)), r.Long(2), r.String(3).Trim(), r.NullableString(4), r.NullableString(5), r.Long(6), r.NullableString(7), r.NullableString(8), r.NullableString(9), r.NullableString(10), r.NullableInt(11) ?? 0, r.NullableString(12)), ct).ConfigureAwait(false); return new ActivitySnapshot @@ -83,5 +77,21 @@ public sealed class ActivityCollector(int transactionMinutes = 10, int transacti }; } + /// as sent: transactions holding at least this much log are listed first. + internal static string BuildTransactionsSql(int transactionLogMegabytes) + { + var logBytes = (long)transactionLogMegabytes * 1024 * 1024; + return TransactionsSql.Replace("{notAgent}", NotAgent, StringComparison.Ordinal) + .Replace("{logBytes}", logBytes.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); + } + + /// as sent: statements running longer than this many minutes. + internal static string BuildLongRequestsSql(int requestMinutes) + { + var thresholdMs = (long)requestMinutes * 60_000; + return LongRequestsSql.Replace("{notAgent}", NotAgent, StringComparison.Ordinal) + .Replace("{thresholdMs}", thresholdMs.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); + } + private static DateTimeOffset Utc(DateTime value) => new(DateTime.SpecifyKind(value, DateTimeKind.Utc)); } diff --git a/src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs b/src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs index 33cfc54..a0d7c17 100644 --- a/src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs +++ b/src/SQLInsight.Collectors/Custom/CustomQueryCollector.cs @@ -13,6 +13,13 @@ public sealed class CustomQueryCollector(CustomRuleCatalog catalog) : Collector< /// SQL Server error numbers that mean the login lacks a permission or access to a database. private static readonly HashSet PermissionErrors = [229, 230, 262, 297, 300, 916, 4060, 15247, 15151]; + /// The online user databases (not snapshots) the agent can open: database-scoped rules run in each. + internal const string DatabasesSql = + "SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name"; + + /// What the collector sends for one rule: switch to the database (master for server-scoped rules), then the query. + internal static string BuildRuleSql(string database, string query) => "USE " + Db.Identifier(database) + ";\n" + query; + public override string Name => "custom-rules"; public override TimeSpan DefaultInterval => TimeSpan.FromMinutes(15); @@ -26,7 +33,7 @@ public sealed class CustomQueryCollector(CustomRuleCatalog catalog) : Collector< return new CustomQueryResults(results); } - var original = (await Db.QueryAsync(context.Connection, "SELECT DB_NAME()", r => r.String(0), context.CancellationToken).ConfigureAwait(false))[0]; + var original = (await Db.QueryAsync(context.Connection, Db.CurrentDatabaseSql, r => r.String(0), context.CancellationToken).ConfigureAwait(false))[0]; try { List? databases = null; @@ -38,9 +45,7 @@ public sealed class CustomQueryCollector(CustomRuleCatalog catalog) : Collector< continue; } - databases ??= await Db.QueryAsync(context.Connection, - "SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND source_database_id IS NULL AND HAS_DBACCESS(name) = 1 ORDER BY name", - r => r.String(0), context.CancellationToken).ConfigureAwait(false); + databases ??= await Db.QueryAsync(context.Connection, DatabasesSql, r => r.String(0), context.CancellationToken).ConfigureAwait(false); results[rule.Id] = await RunPerDatabaseAsync(context, rule, databases).ConfigureAwait(false); } } @@ -88,7 +93,7 @@ public sealed class CustomQueryCollector(CustomRuleCatalog catalog) : Collector< try { await using var command = context.Connection.CreateCommand(); - command.CommandText = "USE " + Db.Identifier(database) + ";\n" + rule.Query; + command.CommandText = BuildRuleSql(database, rule.Query); command.CommandTimeout = Db.CommandTimeoutSeconds; await using var reader = await command.ExecuteReaderAsync(context.CancellationToken).ConfigureAwait(false); var rows = new List>(); diff --git a/src/SQLInsight.Collectors/Databases/DatabaseCollector.cs b/src/SQLInsight.Collectors/Databases/DatabaseCollector.cs index c8c4042..db00f8d 100644 --- a/src/SQLInsight.Collectors/Databases/DatabaseCollector.cs +++ b/src/SQLInsight.Collectors/Databases/DatabaseCollector.cs @@ -47,7 +47,7 @@ public sealed class DatabaseCollector : Collector } // msdb and DATABASEPROPERTYEX report server local time; everything downstream works in UTC. - var offsetMinutes = (await Db.QueryAsync(connection, "SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())", r => r.Int(0), ct).ConfigureAwait(false))[0]; + var offsetMinutes = (await Db.QueryAsync(connection, Db.UtcOffsetSql, r => r.Int(0), ct).ConfigureAwait(false))[0]; DateTime? ToUtc(DateTime? local) => local is { Year: > 1900 } value ? DateTime.SpecifyKind(value.AddMinutes(-offsetMinutes), DateTimeKind.Utc) : local; var vlfs = context.Capabilities.Has(Capability.LogInfoDmv) @@ -206,17 +206,18 @@ public sealed class DatabaseCollector : Collector private static bool ShouldReadQueryStore(DatabaseInfo database) => database.State == "ONLINE" && !database.IsSystemDatabase && !database.IsReplicaOrMirror; + internal static string BuildQueryStoreSql(string database) => $""" + SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb + FROM {Db.Identifier(database)}.sys.database_query_store_options + """; + private static async Task ReadQueryStoreAsync(DbConnection connection, string database, CancellationToken ct) { - var sql = $""" - SELECT desired_state_desc, actual_state_desc, readonly_reason, current_storage_size_mb, max_storage_size_mb - FROM {Db.Identifier(database)}.sys.database_query_store_options - """; try { return await Db.QuerySingleAsync( connection, - sql, + BuildQueryStoreSql(database), r => new QueryStoreState(r.String(0), r.String(1), r.Int(2), r.Long(3), r.Long(4)), ct).ConfigureAwait(false); } diff --git a/src/SQLInsight.Collectors/Db.cs b/src/SQLInsight.Collectors/Db.cs index f9588ba..3a3620c 100644 --- a/src/SQLInsight.Collectors/Db.cs +++ b/src/SQLInsight.Collectors/Db.cs @@ -60,9 +60,18 @@ internal static class Db _ => "(SELECT login_time FROM master.dbo.sysprocesses WHERE spid = 1)", }; + /// The query for when the instance started (see ). + public static string StartTimeSql(Core.Instances.SqlServerVersion version) => "SELECT " + StartTimeExpression(version); + + /// The current database, so a collector that switches database with USE can switch back. + public const string CurrentDatabaseSql = "SELECT DB_NAME()"; + + /// Minutes between the server's local time and UTC. + public const string UtcOffsetSql = "SELECT DATEDIFF(minute, GETUTCDATE(), GETDATE())"; + public static async Task StartTimeAsync(DbConnection connection, Core.Instances.SqlServerVersion version, CancellationToken cancellationToken) { - var rows = await QueryAsync(connection, "SELECT " + StartTimeExpression(version), r => r.DateTime(0), cancellationToken).ConfigureAwait(false); + var rows = await QueryAsync(connection, StartTimeSql(version), r => r.DateTime(0), cancellationToken).ConfigureAwait(false); return rows[0]; } } diff --git a/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs b/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs index a00e3e5..4aa06c6 100644 --- a/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs +++ b/src/SQLInsight.Collectors/Instance/ErrorLogCollector.cs @@ -117,7 +117,7 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : Collector (Utc(r.DateTime(0)) - offset, r.NullableString(2) ?? string.Empty), cancellationToken).ConfigureAwait(false); return [.. ErrorLogAnalysis.ParseLines(lines.Where(l => l.Item1 > since).Select(l => (l.Item1, l.Item2)))]; } @@ -145,12 +145,7 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : Collector '{Local(since, offset)}' - """; - var rows = await Db.QueryAsync(connection, sql, r => (At: Utc(r.DateTime(0)) - offset, Class: r.Int(1), Error: r.NullableInt(2), Severity: r.NullableInt(3), + var rows = await Db.QueryAsync(connection, BuildDefaultTraceSql(files, Local(since, offset)), r => (At: Utc(r.DateTime(0)) - offset, Class: r.Int(1), Error: r.NullableInt(2), Severity: r.NullableInt(3), Text: r.NullableString(4)), cancellationToken).ConfigureAwait(false); var entries = new List(); foreach (var row in rows) @@ -179,6 +174,17 @@ public sealed class ErrorLogCollector(int loginFailureBurst = 20) : CollectorThe current error log (0), SQL Server's (1), between two server local times. + internal static string BuildErrorLogSql(string fromLocal, string toLocal) => + $"EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, '{fromLocal}', '{toLocal}'"; + + /// The arguments to fn_trace_gettable: a file literal and 1 (that file) or DEFAULT (all rollover files). + internal static string BuildDefaultTraceSql(string files, string sinceLocal) => $""" + SELECT StartTime, EventClass, Error, Severity, CAST(TextData AS nvarchar(2000)) + FROM sys.fn_trace_gettable({files}) + WHERE EventClass IN (20, 22) AND StartTime > '{sinceLocal}' + """; + private static string Local(DateTimeOffset utc, TimeSpan offset) => (utc.UtcDateTime + offset).ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); diff --git a/src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs b/src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs index c5e15b3..7b7be54 100644 --- a/src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs +++ b/src/SQLInsight.Collectors/Instance/InstanceConfigurationCollector.cs @@ -29,6 +29,15 @@ public sealed class InstanceConfigurationCollector : CollectorWhether this build has the instant_file_initialization_enabled column. + internal static string BuildServicesSql(bool instantFileInitialization) + { + var ifi = instantFileInitialization ? "instant_file_initialization_enabled" : "CAST(NULL AS nvarchar(1))"; + return $"SELECT servicename, service_account, startup_type_desc, status_desc, {ifi} FROM sys.dm_server_services"; + } + public override string Name => "instance-config"; public override TimeSpan DefaultInterval => TimeSpan.FromMinutes(15); @@ -60,12 +69,10 @@ public sealed class InstanceConfigurationCollector : Collector (Ifi: r.NullableInt(0) is not null, Memory: r.NullableInt(1) is not null), ct).ConfigureAwait(false)).FirstOrDefault(); - var ifi = columns.Ifi ? "instant_file_initialization_enabled" : "CAST(NULL AS nvarchar(1))"; - var services = await Db.QueryAsync(connection, - $"SELECT servicename, service_account, startup_type_desc, status_desc, {ifi} FROM sys.dm_server_services", + var services = await Db.QueryAsync(connection, BuildServicesSql(columns.Ifi), r => new ServiceInfo(r.String(0), r.NullableString(1), r.NullableString(2), r.NullableString(3), YesNo(r.NullableString(4))), ct).ConfigureAwait(false); var memoryModel = columns.Memory - ? (await Db.QueryAsync(connection, "SELECT sql_memory_model_desc FROM sys.dm_os_sys_info", r => r.NullableString(0), ct).ConfigureAwait(false)).FirstOrDefault() + ? (await Db.QueryAsync(connection, MemoryModelSql, r => r.NullableString(0), ct).ConfigureAwait(false)).FirstOrDefault() : null; snapshot = snapshot with { diff --git a/src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs b/src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs index ec9eca6..e26a9fa 100644 --- a/src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs +++ b/src/SQLInsight.Collectors/Instance/PrivilegeCollector.cs @@ -40,6 +40,13 @@ public sealed class PrivilegeCollector(IGroupMemberResolver? groups = null, int SELECT path, DATEDIFF(minute, GETUTCDATE(), GETDATE()) FROM sys.traces WHERE is_default = 1 """; + /// Role membership events (104, 105, 108) in all the default trace's files since a server local time. + internal static string BuildTraceSql(string traceFileBase, string sinceLocal) => $""" + SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName + FROM sys.fn_trace_gettable({Db.Literal(traceFileBase)}, DEFAULT) + WHERE EventClass IN (104, 105, 108) AND StartTime >= '{sinceLocal}' + """; + private readonly IGroupMemberResolver _groups = groups ?? new NoGroupResolver(); public override string Name => "privileges"; @@ -124,11 +131,7 @@ public sealed class PrivilegeCollector(IGroupMemberResolver? groups = null, int var offset = TimeSpan.FromMinutes(trace.Offset); var sinceLocal = (since.UtcDateTime + offset).ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); - var sql = $""" - SELECT StartTime, EventClass, EventSubClass, LoginName, TargetLoginName, RoleName - FROM sys.fn_trace_gettable({Db.Literal(PrivilegeAnalysis.TraceFileBase(trace.Path))}, DEFAULT) - WHERE EventClass IN (104, 105, 108) AND StartTime >= '{sinceLocal}' - """; + var sql = BuildTraceSql(PrivilegeAnalysis.TraceFileBase(trace.Path), sinceLocal); var events = await Db.QueryAsync(connection, sql, r => new TraceSecurityEvent( new DateTimeOffset(DateTime.SpecifyKind(r.DateTime(0) - offset, DateTimeKind.Utc)), r.Int(1), r.NullableInt(2) ?? 0, r.NullableString(3), r.NullableString(4), r.NullableString(5)), cancellationToken).ConfigureAwait(false); diff --git a/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs b/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs index 2a21dcf..0fbdbe3 100644 --- a/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs +++ b/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs @@ -42,7 +42,7 @@ public sealed class ReplicationCollector : Collector return new ReplicationSnapshot { CapturedAt = context.Now }; } - var original = (await Db.QueryAsync(connection, "SELECT DB_NAME()", r => r.String(0), ct).ConfigureAwait(false))[0]; + var original = (await Db.QueryAsync(connection, Db.CurrentDatabaseSql, r => r.String(0), ct).ConfigureAwait(false))[0]; try { var use = "USE " + Db.Identifier(distribution) + ";\n"; diff --git a/src/SQLInsight.Collectors/Instance/ServerCollector.cs b/src/SQLInsight.Collectors/Instance/ServerCollector.cs index 21ec809..feb83f1 100644 --- a/src/SQLInsight.Collectors/Instance/ServerCollector.cs +++ b/src/SQLInsight.Collectors/Instance/ServerCollector.cs @@ -17,20 +17,7 @@ public sealed class ServerCollector : Collector return await CollectSql2000Async(context).ConfigureAwait(false); } - var memory = version.Major >= 11 ? "physical_memory_kb / 1024" : "physical_memory_in_bytes / 1048576"; - var vmType = version >= new Core.Instances.SqlServerVersion(10, 50, 2500, 0) ? "virtual_machine_type_desc" : "CAST(NULL AS nvarchar(60))"; - var sql = $""" - SELECT - {Db.StartTimeExpression(version)}, - cpu_count, - (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), - (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), - {memory}, - {vmType} - FROM sys.dm_os_sys_info - """; - - var snapshot = await Db.QuerySingleAsync(context.Connection, sql, r => new ServerSnapshot + var snapshot = await Db.QuerySingleAsync(context.Connection, BuildSql(version), r => new ServerSnapshot { CapturedAt = context.Now, SqlServerStartTime = r.DateTime(0), @@ -44,13 +31,33 @@ public sealed class ServerCollector : Collector return snapshot ?? throw new InvalidOperationException("sys.dm_os_sys_info returned no rows."); } + /// The query for SQL Server 2005 and later. + internal static string BuildSql(Core.Instances.SqlServerVersion version) + { + var memory = version.Major >= 11 ? "physical_memory_kb / 1024" : "physical_memory_in_bytes / 1048576"; + var vmType = version >= new Core.Instances.SqlServerVersion(10, 50, 2500, 0) ? "virtual_machine_type_desc" : "CAST(NULL AS nvarchar(60))"; + return $""" + SELECT + {Db.StartTimeExpression(version)}, + cpu_count, + (SELECT COUNT(*) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + (SELECT COUNT(DISTINCT parent_node_id) FROM sys.dm_os_schedulers WHERE status = N'VISIBLE ONLINE'), + {memory}, + {vmType} + FROM sys.dm_os_sys_info + """; + } + + /// SQL Server 2000: processor count and memory (Index, Name, Internal_Value, Character_Value). + internal const string Sql2000 = "EXEC master.dbo.xp_msver"; + private static async Task CollectSql2000Async(CollectorContext context) { var ct = context.CancellationToken; var startTime = await Db.StartTimeAsync(context.Connection, context.Instance.Version, ct).ConfigureAwait(false); // xp_msver: Index, Name, Internal_Value, Character_Value. - var msver = await Db.QueryAsync(context.Connection, "EXEC master.dbo.xp_msver", r => (Name: r.String(1), Value: r.NullableLong(2)), ct) + var msver = await Db.QueryAsync(context.Connection, Sql2000, r => (Name: r.String(1), Value: r.NullableLong(2)), ct) .ConfigureAwait(false); long? Get(string name) => msver.FirstOrDefault(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)).Value; diff --git a/src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs b/src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs index e93b0db..7d9a568 100644 --- a/src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs +++ b/src/SQLInsight.Collectors/Instance/TraceFlagCollector.cs @@ -5,6 +5,8 @@ namespace SQLInsight.Collectors.Instance; public sealed class TraceFlagCollector : Collector { + internal const string Sql = "DBCC TRACESTATUS(-1) WITH NO_INFOMSGS"; + public override string Name => "trace-flags"; public override TimeSpan DefaultInterval => TimeSpan.FromMinutes(15); @@ -17,7 +19,7 @@ public sealed class TraceFlagCollector : Collector // 2005+: TraceFlag, Status, Global, Session. 2000: TraceFlag, Status (only global flags are listed with -1). var rows = await Db.QueryAsync( connection, - "DBCC TRACESTATUS(-1) WITH NO_INFOMSGS", + Sql, r => (Flag: r.Int(0), Enabled: r.Bool(1), Global: r.FieldCount < 3 || r.Bool(2)), cancellationToken).ConfigureAwait(false); diff --git a/src/SQLInsight.Collectors/Performance/CpuCollector.cs b/src/SQLInsight.Collectors/Performance/CpuCollector.cs index 6fe402f..9d0cbf5 100644 --- a/src/SQLInsight.Collectors/Performance/CpuCollector.cs +++ b/src/SQLInsight.Collectors/Performance/CpuCollector.cs @@ -24,6 +24,9 @@ public sealed class CpuCollector : Collector ORDER BY rb.[timestamp] DESC """; + /// Windows or Linux (SQL Server 2017+). + internal const string HostPlatformSql = "SELECT host_platform FROM sys.dm_os_host_info"; + public override string Name => "cpu"; public override TimeSpan DefaultInterval => TimeSpan.FromMinutes(5); public override IReadOnlyCollection RequiredCapabilities => [Capability.DynamicManagementViews]; @@ -38,7 +41,7 @@ public sealed class CpuCollector : Collector samples.Reverse(); var platform = context.Instance.Version.Major >= 14 - ? (await Db.QueryAsync(context.Connection, "SELECT host_platform FROM sys.dm_os_host_info", r => r.NullableString(0), context.CancellationToken).ConfigureAwait(false)).FirstOrDefault() + ? (await Db.QueryAsync(context.Connection, HostPlatformSql, r => r.NullableString(0), context.CancellationToken).ConfigureAwait(false)).FirstOrDefault() : "Windows"; return new CpuUtilization { CapturedAt = context.Now, Samples = samples, HostPlatform = platform }; } diff --git a/src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs b/src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs index 35d106a..af6f94b 100644 --- a/src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs +++ b/src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs @@ -72,6 +72,9 @@ public sealed class QueryStatsCollector(double activityThresholdPercent = 50) : 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 """; + /// The server clock, when no statement ran since the previous collection. + internal const string ServerTimeSql = "SELECT GETDATE()"; + public override string Name => "query-stats"; public override TimeSpan DefaultInterval => TimeSpan.FromMinutes(5); public override IReadOnlyCollection RequiredCapabilities => [Capability.DynamicManagementViews]; @@ -106,7 +109,7 @@ public sealed class QueryStatsCollector(double activityThresholdPercent = 50) : }, ct).ConfigureAwait(false); // No statement ran since the previous collection: the server clock still has to move forward. - serverTime ??= (await Db.QueryAsync(context.Connection, "SELECT GETDATE()", r => r.DateTime(0), ct).ConfigureAwait(false))[0]; + serverTime ??= (await Db.QueryAsync(context.Connection, ServerTimeSql, r => r.DateTime(0), ct).ConfigureAwait(false))[0]; var fetched = new QueryStatsSnapshot { diff --git a/tests/SQLInsight.Collectors.Tests/SQLInsight.Collectors.Tests.csproj b/tests/SQLInsight.Collectors.Tests/SQLInsight.Collectors.Tests.csproj index 9693431..22a131c 100644 --- a/tests/SQLInsight.Collectors.Tests/SQLInsight.Collectors.Tests.csproj +++ b/tests/SQLInsight.Collectors.Tests/SQLInsight.Collectors.Tests.csproj @@ -7,6 +7,9 @@ + + + diff --git a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs new file mode 100644 index 0000000..986b892 --- /dev/null +++ b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptSources.cs @@ -0,0 +1,328 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using SQLInsight.Collectors.Concurrency; +using SQLInsight.Collectors.Custom; +using SQLInsight.Collectors.Databases; +using SQLInsight.Collectors.Instance; +using SQLInsight.Collectors.Performance; +using SQLInsight.Core.Instances; + +namespace SQLInsight.Collectors.Tests.SqlScripts; + +/// +/// Every SQL text the agent sends to a monitored instance, as the code builds it, keyed as the scripts in the folder +/// "SQL Scripts" name it on their -- Kilde: <key> lines. Builders are called with the same arguments as the +/// scripts show (defaults from the agent's configuration, the database msdb). Values the agent computes at run time (a +/// trace file path, a point in time) are rendered as tokens ⟪name⟫: a script may put anything there, typically a variable +/// declared above it so the script runs in SSMS. +/// +internal static class SqlScriptSources +{ + /// The database per-database queries are shown for: it exists on every instance, so the scripts run as they are. + public const string ExampleDatabase = "msdb"; + + /// What the script's -- Kilde: line says. + /// The collector (or InstanceProbe/connection) that sends it. + /// The SQL Server versions or situation the text is sent for. + /// The source file, relative to the repository root. + /// The constant or method holding the text. + public sealed record Source(string Key, string Collector, string Variant, string File, string Member, Func Render) + { + public string Sql => Normalize(Render()); + } + + private static readonly SqlServerVersion V2000 = SqlServerVersion.Parse("8.0.2039.0"); + private static readonly SqlServerVersion V2005 = SqlServerVersion.Parse("9.0.5000.0"); + private static readonly SqlServerVersion V2008 = SqlServerVersion.Parse("10.0.6000.29"); + private static readonly SqlServerVersion V2008R2Sp1 = SqlServerVersion.Parse("10.50.2500.0"); + private static readonly SqlServerVersion V2012 = SqlServerVersion.Parse("11.0.7001.0"); + private static readonly SqlServerVersion V2016Sp2 = SqlServerVersion.Parse("13.0.5026.0"); + + private const string C = "src/SQLInsight.Collectors/"; + + public static IReadOnlyList All { get; } = Create(); + + public static IReadOnlyDictionary ByKey { get; } = All.ToDictionary(s => s.Key, StringComparer.Ordinal); + + public static IEnumerable For(string collector) => All.Where(s => s.Collector == collector); + + /// LF line endings: the sources and the scripts may be checked out with CRLF. + public static string Normalize(string sql) => sql.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n'); + + /// A regex that finds in a script, with any text where it has tokens. + public static Regex Pattern(string sql) + { + var parts = Regex.Split(Normalize(sql), "⟪[a-z]+⟫"); + return new Regex(string.Join("(?:.+?)", parts.Select(Regex.Escape)), RegexOptions.Singleline | RegexOptions.CultureInvariant); + } + + private static string Tokens(string sql, params string[] names) + { + foreach (var name in names) + { + sql = sql.Replace("N'§" + name + "§'", "⟪" + name + "⟫", StringComparison.Ordinal) + .Replace("'§" + name + "§'", "⟪" + name + "⟫", StringComparison.Ordinal) + .Replace("§" + name + "§", "⟪" + name + "⟫", StringComparison.Ordinal); + } + + return sql; + } + + private static string Internal(Type type, string field) => + (string)(type.GetField(field, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)?.GetRawConstantValue() + ?? throw new InvalidOperationException($"{type.Name}.{field} findes ikke.")); + + private static InstanceInfo Instance(SqlServerVersion version) => new() + { + ServerName = "eksempel", + Version = version, + Edition = "Enterprise Edition", + EngineEdition = EngineEdition.Enterprise, + IsHadrEnabled = version.Major >= 11, + }; + + private static string DatabaseSql2005(SqlServerVersion version) + { + var instance = Instance(version); + return DatabaseCollector.BuildSql2005(instance, InstanceCapabilities.From(instance)); + } + + private static List Create() + { + var probe = typeof(Connectivity.InstanceProbe); + var factory = typeof(Connectivity.SqlConnectionFactory); + var sources = new List + { + // Connection and registration. + new("SqlConnectionFactory.SessionSettings", "InstanceProbe", "Alle versioner: sættes på hver forbindelse, agenten åbner", + "src/SQLInsight.Connectivity/SqlConnectionFactory.cs", "SqlConnectionFactory.SessionSettings", () => Internal(factory, "SessionSettings")), + new("InstanceProbe.Query", "InstanceProbe", "Alle versioner (SQL Server 2000 og nyere)", + "src/SQLInsight.Connectivity/InstanceProbe.cs", "InstanceProbe.Query", () => Internal(probe, "Query")), + + // Server. + new("ServerCollector.BuildSql(2012+)", "ServerCollector", "SQL Server 2012 og nyere", + C + "Instance/ServerCollector.cs", "ServerCollector.BuildSql(version)", () => ServerCollector.BuildSql(V2012)), + new("ServerCollector.BuildSql(2008 R2 SP1)", "ServerCollector", "SQL Server 2008 R2 SP1 og senere 2008 R2-builds", + C + "Instance/ServerCollector.cs", "ServerCollector.BuildSql(version)", () => ServerCollector.BuildSql(V2008R2Sp1)), + new("ServerCollector.BuildSql(2008)", "ServerCollector", "SQL Server 2008 og 2008 R2 før SP1", + C + "Instance/ServerCollector.cs", "ServerCollector.BuildSql(version)", () => ServerCollector.BuildSql(V2008)), + new("ServerCollector.BuildSql(2005)", "ServerCollector", "SQL Server 2005", + C + "Instance/ServerCollector.cs", "ServerCollector.BuildSql(version)", () => ServerCollector.BuildSql(V2005)), + StartTime("ServerCollector", V2000, "SQL Server 2000 (legacy ODBC-sti): starttidspunkt"), + new("ServerCollector.Sql2000", "ServerCollector", "SQL Server 2000 (legacy ODBC-sti): CPU og hukommelse", + C + "Instance/ServerCollector.cs", "ServerCollector.Sql2000", () => ServerCollector.Sql2000), + + // Configuration and trace flags. + new("ConfigurationCollector.Sql2005", "ConfigurationCollector", "SQL Server 2005 og nyere", + C + "Instance/ConfigurationCollector.cs", "ConfigurationCollector.Sql2005", () => ConfigurationCollector.Sql2005), + new("ConfigurationCollector.Sql2000", "ConfigurationCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Instance/ConfigurationCollector.cs", "ConfigurationCollector.Sql2000", () => ConfigurationCollector.Sql2000), + new("TraceFlagCollector.Sql", "TraceFlagCollector", "Alle versioner (SQL Server 2000 og nyere)", + C + "Instance/TraceFlagCollector.cs", "TraceFlagCollector.Sql", () => TraceFlagCollector.Sql), + + // Instance configuration (the configuration page and its change log). + new("InstanceConfigurationCollector.PropertiesSql", "InstanceConfigurationCollector", "Alle versioner", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.PropertiesSql", () => InstanceConfigurationCollector.PropertiesSql), + new("InstanceConfigurationCollector.TempdbSql", "InstanceConfigurationCollector", "SQL Server 2005 og nyere", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.TempdbSql", () => InstanceConfigurationCollector.TempdbSql), + new("InstanceConfigurationCollector.ColumnsSql", "InstanceConfigurationCollector", "SQL Server 2008 R2 SP1 og nyere", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.ColumnsSql", () => InstanceConfigurationCollector.ColumnsSql), + new("InstanceConfigurationCollector.BuildServicesSql(med IFI)", "InstanceConfigurationCollector", "SQL Server 2008 R2 SP1 og nyere, builds med kolonnen instant_file_initialization_enabled (se ColumnsSql)", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.BuildServicesSql(true)", () => InstanceConfigurationCollector.BuildServicesSql(true)), + new("InstanceConfigurationCollector.BuildServicesSql(uden IFI)", "InstanceConfigurationCollector", "SQL Server 2008 R2 SP1 og nyere, builds uden kolonnen instant_file_initialization_enabled", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.BuildServicesSql(false)", () => InstanceConfigurationCollector.BuildServicesSql(false)), + new("InstanceConfigurationCollector.MemoryModelSql", "InstanceConfigurationCollector", "Builds med kolonnen sql_memory_model_desc (se ColumnsSql)", + C + "Instance/InstanceConfigurationCollector.cs", "InstanceConfigurationCollector.MemoryModelSql", () => InstanceConfigurationCollector.MemoryModelSql), + + // Privileges. + new("PrivilegeCollector.MembersSql", "PrivilegeCollector", "SQL Server 2005 og nyere", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.MembersSql", () => PrivilegeCollector.MembersSql), + new("PrivilegeCollector.SqlLoginsSql", "PrivilegeCollector", "SQL Server 2005 og nyere", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.SqlLoginsSql", () => PrivilegeCollector.SqlLoginsSql), + new("PrivilegeCollector.DefaultTraceEnabledSql", "PrivilegeCollector", "SQL Server 2005 og nyere", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.DefaultTraceEnabledSql", () => PrivilegeCollector.DefaultTraceEnabledSql), + new("PrivilegeCollector.DefaultTracePathSql", "PrivilegeCollector", "SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.DefaultTracePathSql", () => PrivilegeCollector.DefaultTracePathSql), + new("PrivilegeCollector.BuildTraceSql", "PrivilegeCollector", "SQL Server 2005 og nyere, kun når der er fundet ændringer (kræver ALTER TRACE)", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.BuildTraceSql(traceFileBase, sinceLocal)", + () => Tokens(PrivilegeCollector.BuildTraceSql("§fil§", "§siden§"), "fil", "siden")), + + // Databases. + new("DatabaseCollector.BuildSql2005(2016 SP2+)", "DatabaseCollector", "SQL Server 2016 SP2 og nyere (med LastGoodCheckDbTime og availability groups)", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.BuildSql2005(instance, capabilities)", () => DatabaseSql2005(V2016Sp2)), + new("DatabaseCollector.BuildSql2005(2012)", "DatabaseCollector", "SQL Server 2012 – 2016 SP1", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.BuildSql2005(instance, capabilities)", () => DatabaseSql2005(V2012)), + new("DatabaseCollector.BuildSql2005(2005)", "DatabaseCollector", "SQL Server 2005 – 2008 R2", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.BuildSql2005(instance, capabilities)", () => DatabaseSql2005(V2008R2Sp1)), + new("DatabaseCollector.BuildSql2000", "DatabaseCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.BuildSql2000()", DatabaseCollector.BuildSql2000), + new("DatabaseCollector.Files2005", "DatabaseCollector", "SQL Server 2005 og nyere", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.Files2005", () => DatabaseCollector.Files2005), + new("DatabaseCollector.Files2000", "DatabaseCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.Files2000", () => DatabaseCollector.Files2000), + new("Db.UtcOffsetSql", "DatabaseCollector", "Alle versioner: serverens afstand til UTC (msdb-tider er lokal tid)", + C + "Db.cs", "Db.UtcOffsetSql", () => Db.UtcOffsetSql), + new("DatabaseCollector.VlfSql", "DatabaseCollector", "SQL Server 2016 SP2 og nyere (sys.dm_db_log_info)", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.VlfSql", () => DatabaseCollector.VlfSql), + new("DatabaseCollector.BuildQueryStoreSql", "DatabaseCollector", "SQL Server 2016 og nyere: én gang pr. online brugerdatabase (vist for " + ExampleDatabase + ")", + C + "Databases/DatabaseCollector.cs", "DatabaseCollector.BuildQueryStoreSql(database)", () => DatabaseCollector.BuildQueryStoreSql(ExampleDatabase)), + new("SuspectPageCollector.Sql", "SuspectPageCollector", "SQL Server 2005 og nyere", + C + "Instance/SuspectPageCollector.cs", "SuspectPageCollector.Sql", () => SuspectPageCollector.Sql), + + // Plan cache, waits, I/O. + new("PlanCacheCollector.Sql", "PlanCacheCollector", "SQL Server 2005 og nyere", + C + "Instance/PlanCacheCollector.cs", "PlanCacheCollector.Sql", () => PlanCacheCollector.Sql), + StartTime("WaitStatsCollector", V2008, "SQL Server 2008 og nyere: starttidspunkt (nulstilling af kumulative tal)"), + StartTime("WaitStatsCollector", V2005, "SQL Server 2005: starttidspunkt"), + StartTime("WaitStatsCollector", V2000, "SQL Server 2000 (legacy ODBC-sti): starttidspunkt"), + new("WaitStatsCollector.Sql2005", "WaitStatsCollector", "SQL Server 2005 og nyere", + C + "Performance/WaitStatsCollector.cs", "WaitStatsCollector.Sql2005", () => WaitStatsCollector.Sql2005), + new("WaitStatsCollector.Sql2000", "WaitStatsCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Performance/WaitStatsCollector.cs", "WaitStatsCollector.Sql2000", () => WaitStatsCollector.Sql2000), + StartTime("FileIoCollector", V2008, "SQL Server 2008 og nyere: starttidspunkt"), + StartTime("FileIoCollector", V2005, "SQL Server 2005: starttidspunkt"), + new("FileIoCollector.SqlWithVolumes", "FileIoCollector", "SQL Server 2008 R2 SP1 og nyere (med drev/mount point)", + C + "Performance/FileIoCollector.cs", "FileIoCollector.SqlWithVolumes", () => FileIoCollector.SqlWithVolumes), + new("FileIoCollector.Sql", "FileIoCollector", "SQL Server 2005 – 2008 R2 RTM", + C + "Performance/FileIoCollector.cs", "FileIoCollector.Sql", () => FileIoCollector.Sql), + + // Top SQL and sampling. + StartTime("QueryStatsCollector", V2008, "SQL Server 2008 og nyere: starttidspunkt"), + StartTime("QueryStatsCollector", V2005, "SQL Server 2005: starttidspunkt"), + new("QueryStatsCollector.SqlFor(2008+)", "QueryStatsCollector", "SQL Server 2008 og nyere (første kørsel: de seneste 15 minutter)", + C + "Performance/QueryStatsCollector.cs", "QueryStatsCollector.Sql2008 via SqlFor(version, since: null)", () => QueryStatsCollector.SqlFor(V2008, null)), + new("QueryStatsCollector.SqlFor(2005)", "QueryStatsCollector", "SQL Server 2005 (første kørsel: de seneste 15 minutter)", + C + "Performance/QueryStatsCollector.cs", "QueryStatsCollector.Sql2005 via SqlFor(version, since: null)", () => QueryStatsCollector.SqlFor(V2005, null)), + new("QueryStatsCollector.ServerTimeSql", "QueryStatsCollector", "Alle versioner, kun når ingen sætning har kørt siden sidst", + C + "Performance/QueryStatsCollector.cs", "QueryStatsCollector.ServerTimeSql", () => QueryStatsCollector.ServerTimeSql), + new("WaitSamplingCollector.Sql2008", "WaitSamplingCollector", "SQL Server 2008 og nyere", + C + "Performance/WaitSamplingCollector.cs", "WaitSamplingCollector.Sql2008", () => WaitSamplingCollector.Sql2008), + new("WaitSamplingCollector.Sql2005", "WaitSamplingCollector", "SQL Server 2005", + C + "Performance/WaitSamplingCollector.cs", "WaitSamplingCollector.Sql2005", () => WaitSamplingCollector.Sql2005), + new("LongOperationCollector.Sql", "LongOperationCollector", "SQL Server 2005 og nyere", + C + "Instance/LongOperationCollector.cs", "LongOperationCollector.Sql", () => LongOperationCollector.Sql), + + // Error log. + new("ErrorLogCollector.CanReadErrorLogSql", "ErrorLogCollector", "SQL Server 2005 og nyere", + C + "Instance/ErrorLogCollector.cs", "ErrorLogCollector.CanReadErrorLogSql", () => ErrorLogCollector.CanReadErrorLogSql), + new("ErrorLogCollector.BuildErrorLogSql", "ErrorLogCollector", "SQL Server 2005 og nyere, kilde 1: fejlloggen (når agenten er securityadmin)", + C + "Instance/ErrorLogCollector.cs", "ErrorLogCollector.BuildErrorLogSql(fromLocal, toLocal)", + () => Tokens(ErrorLogCollector.BuildErrorLogSql("§fra§", "§til§"), "fra", "til")), + new("ErrorLogCollector.DefaultTracePathSql", "ErrorLogCollector", "SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)", + C + "Instance/PrivilegeCollector.cs", "PrivilegeCollector.DefaultTracePathSql", () => PrivilegeCollector.DefaultTracePathSql), + new("ErrorLogCollector.BuildDefaultTraceSql", "ErrorLogCollector", "SQL Server 2005 og nyere, kilde 2: default trace (når agenten har ALTER TRACE)", + C + "Instance/ErrorLogCollector.cs", "ErrorLogCollector.BuildDefaultTraceSql(files, sinceLocal)", + () => Tokens(ErrorLogCollector.BuildDefaultTraceSql("§filer§", "§siden§"), "filer", "siden")), + new("ErrorLogCollector.RingBufferSql", "ErrorLogCollector", "SQL Server 2005 og nyere, kilde 3: exception ring buffer (kun VIEW SERVER STATE)", + C + "Instance/ErrorLogCollector.cs", "ErrorLogCollector.RingBufferSql", () => ErrorLogCollector.RingBufferSql), + new("ErrorLogCollector.ConnectivitySql", "ErrorLogCollector", "SQL Server 2008 og nyere, kilde 3: mislykkede logins fra connectivity ring buffer", + C + "Instance/ErrorLogCollector.cs", "ErrorLogCollector.ConnectivitySql", () => ErrorLogCollector.ConnectivitySql), + + // tempdb. + new("TempdbCollector.SpaceSql", "TempdbCollector", "SQL Server 2005 og nyere", + C + "Instance/TempdbCollector.cs", "TempdbCollector.SpaceSql", () => TempdbCollector.SpaceSql), + new("TempdbCollector.SessionsSql", "TempdbCollector", "SQL Server 2005 og nyere", + C + "Instance/TempdbCollector.cs", "TempdbCollector.SessionsSql", () => TempdbCollector.SessionsSql), + new("TempdbCollector.OldestSnapshotSql", "TempdbCollector", "SQL Server 2005 og nyere", + C + "Instance/TempdbCollector.cs", "TempdbCollector.OldestSnapshotSql", () => TempdbCollector.OldestSnapshotSql), + new("TempdbCollector.GeneratorsSql", "TempdbCollector", "SQL Server 2005 og nyere, kun når version store er over halvdelen af grænsen, højst én gang i timen", + C + "Instance/TempdbCollector.cs", "TempdbCollector.GeneratorsSql", () => TempdbCollector.GeneratorsSql), + + // Replication. + new("ReplicationCollector.DistributionDatabaseSql", "ReplicationCollector", "SQL Server 2005 og nyere", + C + "Instance/ReplicationCollector.cs", "ReplicationCollector.DistributionDatabaseSql", () => ReplicationCollector.DistributionDatabaseSql), + new("ReplicationCollector.CurrentDatabaseSql", "ReplicationCollector", "Kun på en distributør: husk databasen, der skal skiftes tilbage til", + C + "Db.cs", "Db.CurrentDatabaseSql", () => Db.CurrentDatabaseSql), + new("ReplicationCollector.AgentsSql", "ReplicationCollector", "Kun på en distributør, i distributionsdatabasen", + C + "Instance/ReplicationCollector.cs", "ReplicationCollector.AgentsSql", () => ReplicationCollector.AgentsSql), + new("ReplicationCollector.ErrorsSql", "ReplicationCollector", "Kun på en distributør, i distributionsdatabasen", + C + "Instance/ReplicationCollector.cs", "ReplicationCollector.ErrorsSql", () => ReplicationCollector.ErrorsSql), + + // Counters and CPU. + StartTime("PerformanceCounterCollector", V2008, "SQL Server 2008 og nyere: starttidspunkt"), + StartTime("PerformanceCounterCollector", V2005, "SQL Server 2005: starttidspunkt"), + StartTime("PerformanceCounterCollector", V2000, "SQL Server 2000 (legacy ODBC-sti): starttidspunkt"), + new("PerformanceCounterCollector.BuildSql(dmv)", "PerformanceCounterCollector", "SQL Server 2005 og nyere", + C + "Performance/PerformanceCounterCollector.cs", "PerformanceCounterCollector.BuildSql(dmv: true)", () => PerformanceCounterCollector.BuildSql(true)), + new("PerformanceCounterCollector.BuildSql(sysperfinfo)", "PerformanceCounterCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Performance/PerformanceCounterCollector.cs", "PerformanceCounterCollector.BuildSql(dmv: false)", () => PerformanceCounterCollector.BuildSql(false)), + new("CpuCollector.Sql", "CpuCollector", "SQL Server 2005 og nyere", + C + "Performance/CpuCollector.cs", "CpuCollector.Sql", () => CpuCollector.Sql), + new("CpuCollector.HostPlatformSql", "CpuCollector", "SQL Server 2017 og nyere (Windows eller Linux)", + C + "Performance/CpuCollector.cs", "CpuCollector.HostPlatformSql", () => CpuCollector.HostPlatformSql), + + // Concurrency. + new("BlockingCollector.BuildSql2005(2012+)", "BlockingCollector", "SQL Server 2012 og nyere", + C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2012)), + new("BlockingCollector.BuildSql2005(2005)", "BlockingCollector", "SQL Server 2005 – 2008 R2", + C + "Concurrency/BlockingCollector.cs", "BlockingCollector.BuildSql2005(version)", () => BlockingCollector.BuildSql2005(V2008R2Sp1)), + new("BlockingCollector.Sql2000", "BlockingCollector", "SQL Server 2000 (legacy ODBC-sti)", + C + "Concurrency/BlockingCollector.cs", "BlockingCollector.Sql2000", () => BlockingCollector.Sql2000), + new("ActivityCollector.BuildTransactionsSql", "ActivityCollector", "SQL Server 2005 og nyere (vist med standardgrænsen 1024 MB log)", + C + "Concurrency/ActivityCollector.cs", "ActivityCollector.BuildTransactionsSql(1024) = TransactionsSql udfyldt", + () => ActivityCollector.BuildTransactionsSql(1024)), + new("ActivityCollector.BuildLongRequestsSql", "ActivityCollector", "SQL Server 2005 og nyere (vist med standardgrænsen 15 minutter)", + C + "Concurrency/ActivityCollector.cs", "ActivityCollector.BuildLongRequestsSql(15) = LongRequestsSql udfyldt", + () => ActivityCollector.BuildLongRequestsSql(15)), + new("DeadlockCollector.BuildFileSql", "DeadlockCollector", "SQL Server 2012 og nyere (system_health-filer; første læsning)", + C + "Concurrency/DeadlockCollector.cs", "DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null)", () => DeadlockCollector.BuildFileSql(null, null)), + new("DeadlockCollector.RingBufferSql", "DeadlockCollector", "SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses", + C + "Concurrency/DeadlockCollector.cs", "DeadlockCollector.RingBufferSql", () => DeadlockCollector.RingBufferSql), + + // Query Store and plans. + new("QueryStoreCollector.DatabasesSql", "QueryStoreCollector", "SQL Server 2016 og nyere", + C + "Performance/QueryStoreCollector.cs", "QueryStoreCollector.DatabasesSql", () => QueryStoreCollector.DatabasesSql), + new("QueryStoreCollector.BuildStatsSql", "QueryStoreCollector", "SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for " + ExampleDatabase + ")", + C + "Performance/QueryStoreCollector.cs", "QueryStoreCollector.BuildStatsSql(database)", () => QueryStoreCollector.BuildStatsSql(ExampleDatabase)), + new("QueryStoreCollector.BuildFailedForcedSql", "QueryStoreCollector", "SQL Server 2016 og nyere: én gang pr. database med Query Store (vist for " + ExampleDatabase + ")", + C + "Performance/QueryStoreCollector.cs", "QueryStoreCollector.BuildFailedForcedSql(database)", () => QueryStoreCollector.BuildFailedForcedSql(ExampleDatabase)), + new("PlanAnalysisCollector.BuildSql(2016 SP2+)", "PlanAnalysisCollector", "SQL Server 2016 SP2 / 2017 CU3 og nyere (med spill- og memory grant-kolonner)", + C + "Performance/PlanAnalysisCollector.cs", "PlanAnalysisCollector.BuildSql(memoryColumns: true, queryHash: true)", () => PlanAnalysisCollector.BuildSql(true, true)), + new("PlanAnalysisCollector.BuildSql(2008)", "PlanAnalysisCollector", "SQL Server 2008 – 2016 SP1 og 2017 før CU3", + C + "Performance/PlanAnalysisCollector.cs", "PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: true)", () => PlanAnalysisCollector.BuildSql(false, true)), + new("PlanAnalysisCollector.BuildSql(2005)", "PlanAnalysisCollector", "SQL Server 2005 SP2 og nyere 2005-builds", + C + "Performance/PlanAnalysisCollector.cs", "PlanAnalysisCollector.BuildSql(memoryColumns: false, queryHash: false)", () => PlanAnalysisCollector.BuildSql(false, false)), + + // Indexes and statistics. + StartTime("IndexCollector", V2008, "SQL Server 2008 og nyere: starttidspunkt"), + StartTime("IndexCollector", V2005, "SQL Server 2005: starttidspunkt"), + new("IndexCollector.DatabasesSql", "IndexCollector", "SQL Server 2005 og nyere", + C + "Databases/IndexCollector.cs", "IndexCollector.DatabasesSql", () => IndexCollector.DatabasesSql), + new("IndexCollector.MissingSql", "IndexCollector", "SQL Server 2005 og nyere", + C + "Databases/IndexCollector.cs", "IndexCollector.MissingSql", () => IndexCollector.MissingSql), + new("IndexCollector.BuildIndexSql(2008+)", "IndexCollector", "SQL Server 2008 og nyere: én gang pr. database (vist for " + ExampleDatabase + ")", + C + "Databases/IndexCollector.cs", "IndexCollector.BuildIndexSql(database, filteredIndexes: true)", () => IndexCollector.BuildIndexSql(ExampleDatabase, true)), + new("IndexCollector.BuildIndexSql(2005)", "IndexCollector", "SQL Server 2005: én gang pr. database (vist for " + ExampleDatabase + ")", + C + "Databases/IndexCollector.cs", "IndexCollector.BuildIndexSql(database, filteredIndexes: false)", () => IndexCollector.BuildIndexSql(ExampleDatabase, false)), + new("IndexCollector.BuildStatisticsSql", "IndexCollector", "SQL Server 2005 og nyere: én gang pr. database (vist for " + ExampleDatabase + ")", + C + "Databases/IndexCollector.cs", "IndexCollector.BuildStatisticsSql(database)", () => IndexCollector.BuildStatisticsSql(ExampleDatabase)), + + // Availability and jobs. + new("AvailabilityGroupCollector.ReplicasSql", "AvailabilityGroupCollector", "SQL Server 2012 og nyere med Always On slået til", + C + "Instance/AvailabilityGroupCollector.cs", "AvailabilityGroupCollector.ReplicasSql", () => AvailabilityGroupCollector.ReplicasSql), + new("AvailabilityGroupCollector.DatabasesSql", "AvailabilityGroupCollector", "SQL Server 2012 og nyere med Always On slået til", + C + "Instance/AvailabilityGroupCollector.cs", "AvailabilityGroupCollector.DatabasesSql", () => AvailabilityGroupCollector.DatabasesSql), + new("AvailabilityGroupCollector.ClusterMembersSql", "AvailabilityGroupCollector", "SQL Server 2012 og nyere med Always On slået til (tom uden Windows-cluster)", + C + "Instance/AvailabilityGroupCollector.cs", "AvailabilityGroupCollector.ClusterMembersSql", () => AvailabilityGroupCollector.ClusterMembersSql), + new("AgentJobCollector.RunningSql", "AgentJobCollector", "SQL Server 2005 og nyere", + C + "Instance/AgentJobCollector.cs", "AgentJobCollector.RunningSql", () => AgentJobCollector.RunningSql), + new("AgentJobCollector.JobsSql", "AgentJobCollector", "SQL Server 2005 og nyere", + C + "Instance/AgentJobCollector.cs", "AgentJobCollector.JobsSql", () => AgentJobCollector.JobsSql), + + // Community and local rules. + new("CustomQueryCollector.CurrentDatabaseSql", "CustomQueryCollector", "Husk databasen, der skal skiftes tilbage til", + C + "Db.cs", "Db.CurrentDatabaseSql", () => Db.CurrentDatabaseSql), + new("CustomQueryCollector.DatabasesSql", "CustomQueryCollector", "Databaserne, database-regler køres i", + C + "Custom/CustomQueryCollector.cs", "CustomQueryCollector.DatabasesSql", () => CustomQueryCollector.DatabasesSql), + new("CustomQueryCollector.BuildRuleSql", "CustomQueryCollector", "Én regel: skift database (master for server-regler, ellers hver brugerdatabase) og kør reglens forespørgsel", + C + "Custom/CustomQueryCollector.cs", "CustomQueryCollector.BuildRuleSql(database, query)", + () => Tokens(CustomQueryCollector.BuildRuleSql("§database§", "§forespoergsel§"), "database", "forespoergsel")), + }; + + return sources; + } + + private static Source StartTime(string collector, SqlServerVersion version, string variant) => new( + $"{collector}.StartTimeSql({(version.Major >= 10 ? "2008+" : version.Major == 9 ? "2005" : "2000")})", + collector, variant, C + "Db.cs", "Db.StartTimeSql(version)", () => Db.StartTimeSql(version)); +} diff --git a/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs new file mode 100644 index 0000000..d402bca --- /dev/null +++ b/tests/SQLInsight.Collectors.Tests/SqlScripts/SqlScriptTests.cs @@ -0,0 +1,221 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using SQLInsight.Rules; +using SQLInsight.Rules.Lifecycle; + +namespace SQLInsight.Collectors.Tests.SqlScripts; + +/// +/// Keeps the folder "SQL Scripts" (one reviewable script per rule, for DBAs) in step with the code: a script for every +/// rule and no other, the collectors' SQL in them word for word, and nothing in them that writes. +/// +public sealed partial class SqlScriptTests +{ + private static readonly string Folder = Path.Combine(RepositoryRoot(), "SQL Scripts"); + + [Fact] + public void Every_registered_rule_has_a_script_and_every_script_a_rule() + { + var ids = BuiltInRules.Create().Select(r => r.Id) + .Concat(RulePack.Embedded().CommunityRules.Select(r => r.Id)) + .ToHashSet(StringComparer.Ordinal); + var files = Directory.EnumerateFiles(Folder, "*.sql", SearchOption.TopDirectoryOnly) + .Select(Path.GetFileNameWithoutExtension) + .OfType() + .ToHashSet(StringComparer.Ordinal); + + var missing = ids.Except(files).Order(StringComparer.Ordinal).ToList(); + var unknown = files.Except(ids).Order(StringComparer.Ordinal).ToList(); + Assert.True(missing.Count == 0, "Regler uden script i \"SQL Scripts\" (tilføj .sql, se CONTRIBUTING.md): " + string.Join(", ", missing)); + Assert.True(unknown.Count == 0, "Scripts i \"SQL Scripts\" for regel-id'er, der ikke findes: " + string.Join(", ", unknown)); + } + + [Fact] + public void Rule_scripts_have_the_review_header() + { + string[] labels = + [ + "-- Regel-id:", "-- Titel:", "-- Alvor:", "-- Versioner:", "-- Hvad tjekkes, og hvorfor:", "-- Vurdering", "-- Grænseværdier", + "-- Rettigheder", "-- Data kommer fra", "-- Regelkode:", "-- Read-only:", + ]; + var problems = new List(); + foreach (var file in RuleScripts()) + { + var text = Read(file); + var id = Path.GetFileNameWithoutExtension(file); + problems.AddRange(labels.Where(l => !text.Contains(l, StringComparison.Ordinal)).Select(l => $"{id}: mangler '{l}'")); + if (!Regex.IsMatch(text, @"^-- Regel-id:\s+" + Regex.Escape(id) + @"\s*$", RegexOptions.Multiline)) + { + problems.Add($"{id}: Regel-id i headeren er ikke filens navn"); + } + } + + Assert.True(problems.Count == 0, string.Join("; ", problems)); + } + + [Fact] + public void Scripts_contain_the_current_sql_from_the_code() + { + var problems = new List(); + foreach (var file in AllScripts()) + { + var name = Path.GetRelativePath(Folder, file); + var blocks = Blocks(Read(file)).ToList(); + if (blocks.Count == 0) + { + problems.Add($"{name}: ingen '-- Kilde:'-blokke"); + } + + foreach (var (key, body) in blocks) + { + if (!SqlScriptSources.ByKey.TryGetValue(key, out var source)) + { + problems.Add($"{name}: ukendt kilde '{key}'"); + } + else if (!SqlScriptSources.Pattern(source.Sql).IsMatch(body)) + { + problems.Add($"{name}: '{key}' er ikke længere den SQL, koden sender ({source.File} : {source.Member})"); + } + } + } + + Assert.True(problems.Count == 0, "Opdatér \"SQL Scripts\": " + string.Join("; ", problems)); + } + + [Fact] + public void Every_collector_query_is_registered_and_in_a_collector_script() + { + var problems = new List(); + var registered = SqlScriptSources.All.Select(s => s.Sql).ToList(); + foreach (var type in typeof(ICollector).Assembly.GetTypes()) + { + const BindingFlags Static = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; + + // SQL constants: found word for word in a registered text ({placeholders} are filled in by the code). + foreach (var field in type.GetFields(Static).Where(f => f.IsLiteral && f.FieldType == typeof(string) && f.Name.Contains("Sql", StringComparison.Ordinal))) + { + var text = SqlScriptSources.Normalize((string)field.GetRawConstantValue()!); + var pattern = new Regex(string.Join("(?:.+?)", Regex.Split(text, @"\{[A-Za-z]+\}").Select(Regex.Escape)), RegexOptions.Singleline); + if (!registered.Any(pattern.IsMatch)) + { + problems.Add($"{type.Name}.{field.Name} er ikke registreret i SqlScriptSources"); + } + } + + // SQL builders: at least one registered rendering. + foreach (var method in type.GetMethods(Static).Where(m => m.ReturnType == typeof(string) && !m.Name.Contains('<', StringComparison.Ordinal) + && (m.Name.StartsWith("Build", StringComparison.Ordinal) || m.Name.EndsWith("Sql", StringComparison.Ordinal) || m.Name == "SqlFor"))) + { + if (!SqlScriptSources.All.Any(s => s.Member.StartsWith(type.Name + ".", StringComparison.Ordinal) && s.Member.Contains(method.Name + "(", StringComparison.Ordinal))) + { + problems.Add($"{type.Name}.{method.Name}() er ikke registreret i SqlScriptSources"); + } + } + } + + var inCollectorScripts = Directory.EnumerateFiles(Path.Combine(Folder, "_collectors"), "*.sql") + .SelectMany(f => Blocks(Read(f)).Select(b => b.Key)) + .ToHashSet(StringComparer.Ordinal); + problems.AddRange(SqlScriptSources.All.Where(s => !inCollectorScripts.Contains(s.Key)).Select(s => $"'{s.Key}' står ikke i _collectors/{s.Collector}.sql")); + + Assert.True(problems.Count == 0, string.Join("; ", problems)); + } + + [Fact] + public void Community_rule_scripts_contain_the_rule_query_verbatim() + { + var problems = RulePack.Embedded().CommunityRules + .Where(r => !File.Exists(Path.Combine(Folder, r.Id + ".sql")) + || !Read(Path.Combine(Folder, r.Id + ".sql")).Contains(SqlScriptSources.Normalize(r.Query), StringComparison.Ordinal)) + .Select(r => r.Id) + .ToList(); + + Assert.True(problems.Count == 0, "Community-reglens query står ikke ordret i SQL Scripts/.sql: " + string.Join(", ", problems)); + } + + [Fact] + public void Scripts_are_read_only_apart_from_the_documented_temp_table() + { + var problems = new List(); + foreach (var file in AllScripts()) + { + var text = Read(file); + var code = CommentsAndStrings().Replace(text, " "); + code = AllowedStatements().Replace(code, " "); + if (text.Contains("-- Kilde: DeadlockCollector.BuildFileSql", StringComparison.Ordinal)) + { + // Documented exception: a session-private temp table in tempdb (see the script's Read-only line). + code = DeadlockTempTable().Replace(code, " "); + } + + problems.AddRange(Writes().Matches(code).Select(m => $"{Path.GetRelativePath(Folder, file)}: {m.Value.ToUpperInvariant()}").Distinct()); + } + + Assert.True(problems.Count == 0, "Skrivende sætninger i \"SQL Scripts\": " + string.Join("; ", problems)); + } + + [Theory] + [InlineData("SELECT name INTO dbo.x FROM sys.objects", true)] + [InlineData("UPDATE dbo.t SET a = 1", true)] + [InlineData("EXEC sys.sp_configure N'x', 1", true)] + [InlineData("SELECT is_auto_update_stats_on, create_date FROM sys.databases -- DROP TABLE x", false)] + [InlineData("EXEC master.dbo.xp_readerrorlog 0, 1, NULL, NULL, @fra, @til", false)] + [InlineData("DBCC TRACESTATUS(-1) WITH NO_INFOMSGS", false)] + [InlineData("SELECT N'DELETE' AS [UPDATE]", false)] + public void The_read_only_check_itself_recognises_writes(string sql, bool writes) => + Assert.Equal(writes, Writes().IsMatch(AllowedStatements().Replace(CommentsAndStrings().Replace(sql, " "), " "))); + + /// The -- Kilde: key blocks: everything after the line up to the next GO. + private static IEnumerable<(string Key, string Body)> Blocks(string text) + { + string? key = null; + var body = new List(); + foreach (var line in text.Split('\n')) + { + if (line.StartsWith("-- Kilde: ", StringComparison.Ordinal)) + { + key = line["-- Kilde: ".Length..].Trim(); + body.Clear(); + } + else if (key is not null && line.Trim() == "GO") + { + yield return (key, string.Join("\n", body)); + key = null; + } + else if (key is not null) + { + body.Add(line); + } + } + } + + private static IEnumerable RuleScripts() => Directory.EnumerateFiles(Folder, "*.sql", SearchOption.TopDirectoryOnly); + + private static IEnumerable AllScripts() => Directory.EnumerateFiles(Folder, "*.sql", SearchOption.AllDirectories); + + private static string Read(string file) => SqlScriptSources.Normalize(File.ReadAllText(file)); + + private static string RepositoryRoot() + { + var root = AppContext.BaseDirectory; + while (!File.Exists(Path.Combine(root, "SQLInsight.slnx"))) + { + root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("SQLInsight.slnx ikke fundet."); + } + + return root; + } + + [GeneratedRegex(@"--[^\n]*|/\*.*?\*/|N?'(?:[^']|'')*'|\[[^\]]*\]", RegexOptions.Singleline)] + private static partial Regex CommentsAndStrings(); + + /// Read-only statements that use otherwise writing keywords. + [GeneratedRegex(@"\bEXEC\s+master\.dbo\.xp_(readerrorlog|msver)\b|\bDBCC\s+TRACESTATUS\b|\bDBCC\s+SQLPERF\s*\(\s*WAITSTATS\s*\)", RegexOptions.IgnoreCase)] + private static partial Regex AllowedStatements(); + + [GeneratedRegex(@"\bINTO\s+#system_health\b|\bDROP\s+TABLE\s+#system_health\b", RegexOptions.IgnoreCase)] + private static partial Regex DeadlockTempTable(); + + [GeneratedRegex(@"\b(INSERT|UPDATE|DELETE|MERGE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|DENY|BACKUP|RESTORE|RECONFIGURE|KILL|SHUTDOWN|BULK|OPENROWSET|OPENQUERY|OPENDATASOURCE|WAITFOR|INTO|EXEC|EXECUTE|DBCC|SETUSER)\b", RegexOptions.IgnoreCase)] + private static partial Regex Writes(); +}