Compare commits

..

21 commits

Author SHA1 Message Date
631edc2adc Review 1.3: SQL Scripts for database discovery and the permission check
Some checks failed
deploy community / deploy (push) Successful in 15s
deploy hub / deploy (push) Successful in 19s
CI / Build og unit tests (push) Successful in 7s
CI / MSI-installer (push) Successful in 17s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled
Registers 1.3's SQL in SqlScriptSources (DatabaseDiscovery: databases 2005/2000
and the Dynamics AX signature per database; PermissionCheck: login/sysadmin, the
HAS_PERMS_BY_NAME checks for 2005-2012, 2014-2019/MI and 2022+, databases without
access) and adds _collectors/DatabaseDiscovery.sql and PermissionCheck.sql with
Danish review headers. The constants are renamed *Sql and the login query moved to
PermissionCheck.WhoSql so the registry test finds them. The scripts of the
collectors that now skip unchosen databases say so (their SQL is unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:26:16 +02:00
473dccafff Version 1.3.0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
12cf5a1e94 Review 1.3: a request that fails in an unexpected way is completed as failed, not left Running
HandleAsync only turned a few exception types into the request's error; anything
else (a lock or command timeout, a JSON or argument error) escaped, leaving the
row Running and the page waiting until housekeeping failed it minutes later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:43 +02:00
a6a72661e7 Review 1.3: a routine discovery that could not be stored is not retried every 2 seconds
When saving a discovery failed in the repository (deadlock victim, lock timeout,
connection), LastDiscoveryAt stayed unset and the next round 2 seconds later
listed the same instance's databases again, loading the customer's server. The
agent now waits at least 15 minutes between routine discoveries of an instance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f0c5d01294 Review 1.3: the overview works before the agent has migrated to 0016
GetInstancesAsync now reads insight.ConfiguredInstances only when it exists, so a
1.3 web UI (e.g. upgraded first on its own server) still shows the overview
against a repository a 1.2 agent has not migrated yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
a66e3f2b3b Review 1.3: upgrade note on AX findings to the hub (now opt-in per database)
1.2 sent Dynamics AX findings to the hub with the rest; 1.3 sends them only for
databases where AX hub sharing is ticked, default off, so sharing stops at the
upgrade until it is turned on. docs/opdatering.md gets an upgrade section that
says so first, with the other 1.3 changes, and the release text points to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
f5ff42ca83 Review 1.3: an unreadable agent key file is kept, not overwritten
LoadOrCreate replaced a key file it could not decrypt, which made the old key
unrecoverable even when only the machine (DPAPI) had changed and the file could
have been put back. The file is now moved aside as credential-key.bin.unreadable-*
and the RSA object of the failed import is disposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
356e102126 Review 1.3: redact logins and user names in the configuration API
API v1 returns logins and people's names as [udeladt] unless
ApiIncludesSensitiveEvidence is on, but /monitored-instances and /agent-requests
returned the SQL login, createdBy/updatedBy/requestedBy/selectionChangedBy, the
login the agent arrived as and 'Login failed for user ...' texts, and the GRANT
script filled in with the login. They are now redacted the same way (the GRANT
script falls back to the shipped template). ChooseAsync also answers 404 for an
unknown instance instead of 422.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
cade91aa4a Review 1.3: a request still waiting for the agent is reused, not queued again
Pressing Test forbindelsen repeatedly, or a script calling the API in a loop,
added a row per call to insight.AgentRequests, each run by the agent in turn. The
same pending request (kind, instance, parameters) is now returned instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
2adeef4597 Review 1.3: local Administrators may change the configuration until AdminGroups is set; MSI property ADMIN_GROUPS
With SQLInsight:Web:AdminGroups empty nobody could add the first server. The
installer now takes ADMIN_GROUPS (';'-separated) and writes it to the web's
sqlinsight.ini; while the setting is empty, members of the web server's
BUILTIN\Administrators (S-1-5-32-544 among the user's group SIDs; a remote
Negotiate login has the full token, a UAC-filtered deny-only SID does not count)
may change it. The page says who may.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
8e1b877fda Review 1.3: overview leaves out removed instances and marks paused ones
The overview kept listing removed and paused instances with their last data and
counted them in the totals and alerts. Removed instances now disappear from the
overview, Alle fund, /api/v1/instances and /metrics (their history stays for
reports and the instance page); paused ones are shown as "Sat på pause" and
are left out of the totals, the urgent findings and the alerts (Prometheus gets
sqlinsight_instance_paused instead of instance_up = 0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
4c65248727 Review 1.3: an instance removed from the configuration file is marked removed, not deleted
The agent deleted ConfigFile rows no longer in the file, and with them (cascade)
the pause and the database choices: commenting an instance out for a restart lost
them. It is now soft-deleted like a Web instance and brought back, choices and
all, when it returns to the file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
01c04ac95a Review 1.3: database choice and discovery take turns (sp_getapplock)
The web's database choice and the agent's discovery both update many rows of an
instance's insight.ConfiguredDatabases in one transaction, in different orders,
which can deadlock. Both now take an application lock per instance first, as
the finding saves do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:42 +02:00
29e1909e98 Review 1.3: one implementation of a service account's SQL login
RepositorySetup.LoginFor/IsLocal duplicated Core's ServiceAccounts, which the web
uses for the GRANT script. Setup now uses ServiceAccounts too, so the login the
installer grants and the one the web shows cannot drift apart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
5ad9355130 Docs: Servere og databaser (1.3)
New docs/servere-og-databaser.md (on the community site too): adding servers,
who may change it (AdminGroups), connection test and GRANT script run by the
agent, database choice, configuration file servers, how SQL login passwords are
sealed and why, the repository grants, and the API. Installation, integration
(scope config:write) and data processing (AX findings to the hub are off by
default) point to it; AdminGroups in the web's appsettings.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
f3ef0cbec7 Web: Servere og databaser, and the same in API v1 (scope config:write)
A page under Overvågning lists the monitored servers (from SQLInsight Web and,
read-only, from the agent's configuration file), adds a server (name\instance,
optional port, Windows via the agent's account or a SQL login, encryption),
pauses, resumes and removes it, and finds instances via the agent (SQL Server
Browser). Each server's page shows the agent's connection test with the rights
it has and lacks, the least-privilege GRANT script for the login the agent
arrives as, and the discovered databases (state, size, recovery model,
compatibility level, Dynamics AX) to tick for monitoring, with "overvåg
automatisk nye databaser" and AX findings to the hub per database. Pages
refresh while the agent works on a request.

Only SQLInsight:Web:AdminGroups (new) may change anything; others see it.
SQL login passwords are sealed with the agent's public key on arrival and
never shown or returned; a new server or login needs the password again.

API v1: /monitored-instances (list, add, get, patch pause/monitor-new,
connection, delete), …/connection-test (start, latest with missing
permissions), …/discovery, …/databases (read, choose), …/grant-script,
/instance-discovery and /agent-requests/{id}. Changes need the new token scope
config:write, or AdminGroups for Windows users.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
81b8376026 Agent: instances from the repository, request queue, sealed passwords, AX hub opt-in
The agent mirrors SQLInsight:Instances into the repository at start (read-only
"fra konfigurationsfil" rows, every database monitored as before) and reads the
configuration on every tick: instances added, paused, removed or changed in
SQLInsight Web, and database choices, take effect without a restart. If the
repository cannot be read before the first load, the configuration file alone
is used.

AgentRequestService runs connection tests (with the permission check), database
discovery and instance discovery (SQL Server Browser, registry) as the agent's
own identity, and refreshes every instance's database list hourly. The agent
keeps an RSA key (DPAPI LocalMachine, restricted folder) and publishes its public
key and identity; SQL login passwords sealed by the web UI are opened only here.

Dynamics AX findings and AX rule outcomes are sent to SQLInsight Hub only for
databases where it is turned on (default off).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
b2fa78411f Collectors: monitor only chosen databases; database discovery and permission check
CollectorContext carries the instance's DatabaseSelection; the database,
index, Query Store and community-rule collectors skip databases that are not
chosen (system databases always run). DatabaseDiscovery lists every database
with state, size, recovery model, compatibility level and access, and
recognises Dynamics AX with the same signature as the AX rules, through
three-part names only (2000 and 2005+ variants). PermissionCheck reports which
rights from grant-least-privilege.sql the login has (HAS_PERMS_BY_NAME,
version-aware; not possible on 2000).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:22:41 +02:00
1199b1aa86 Repository: configured instances, discovered databases, agent request queue (0016)
insight.ConfiguredInstances (Web or ConfigFile source, soft remove, sealed
password), insight.ConfiguredDatabases (discovery result, monitored, AX to hub),
insight.AgentRequests (queue for connection tests and discovery run by the agent)
and insight.AgentKeys (the agent's public key and identity). The web login gets
INSERT on ConfiguredInstances and AgentRequests and UPDATE on named columns only;
no DELETE, no Source/Name change, nothing the agent records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
8776faefb3 Instance configuration core: database selection, sealed credentials, agent request contracts
DatabaseSelection decides which databases the database-specific checks run in
(system databases always), and which may send Dynamics AX findings to the hub
(default none). CredentialEnvelope seals a SQL login password with the agent's
RSA public key (RSA-OAEP-SHA256 + AES-256-GCM), bound to server and login, so only
the agent can open it. Shared contracts for connection tests and discovery, server
address validation, the service account login rule, and the least-privilege GRANT
script filled in for one login.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00
2284e7f082 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/<rule-id>.sql"
holds, per rule (82 built-in, 16 community), a Danish header (what and
why, how C# judges the data, thresholds with config keys, severity,
exact GRANTs, versions, collector file and member) and the collector's
SQL verbatim, every version variant as its own batch.
"_collectors/<Collector>.sql" holds each collector's queries once.

The only statement that is not strictly read-only is documented in the
scripts: the deadlock collector's session temp table (#system_health).

Inline SQL in the collectors moved to internal constants and Build*Sql
methods (no text changed), so SqlScriptTests can check that the scripts
still contain the SQL the code sends, that every rule has a script, that
every collector query is registered, and that nothing writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:20:06 +02:00
207 changed files with 19914 additions and 162 deletions

View file

@ -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.Contracts` | Wire-kontrakt mellem agent og Hub |
| `src/SQLInsight.Hub` | Centralt API til findings, PostgreSQL (Fase 3) | | `src/SQLInsight.Hub` | Centralt API til findings, PostgreSQL (Fase 3) |
| `db/permissions` | Least-privilege-script til agentens login | | `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 | | `tests/` | Unit og integrationstests |
## Sådan skriver du en regel ## 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. 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. 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`. 5. Registrér reglen i `BuiltInRules`.
6. Tilføj `SQL Scripts/<id>.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 ### 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/<id>.sql` med forespørgslen ordret (se fx `SQL Scripts/COM-SEC-001.sql`).
## Queries mod overvågede servere ## 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 **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. - 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. - 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/<Collector>.sql` og regelfilerne, der bruger collectoren. `SqlScriptTests` fejler ellers.
## Commits ## Commits

View file

@ -14,7 +14,7 @@
<Product>SQLInsight Pro</Product> <Product>SQLInsight Pro</Product>
<Copyright>Copyright (c) SEOSoft</Copyright> <Copyright>Copyright (c) SEOSoft</Copyright>
<!-- Release version; set with -p:Version=… when publishing. Update checks compare against it. --> <!-- Release version; set with -p:Version=… when publishing. Update checks compare against it. -->
<Version>1.2.0</Version> <Version>1.3.0</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(MSBuildProjectName.EndsWith('Tests'))"> <PropertyGroup Condition="$(MSBuildProjectName.EndsWith('Tests'))">

View file

@ -23,6 +23,7 @@
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.12" /> <PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.12" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.12" /> <PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.12" />
<PackageVersion Include="System.DirectoryServices.AccountManagement" Version="10.0.12" /> <PackageVersion Include="System.DirectoryServices.AccountManagement" Version="10.0.12" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.12" />
<PackageVersion Include="PDFsharp-MigraDoc" Version="6.2.4" /> <PackageVersion Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageVersion Include="Markdig" Version="1.4.0" /> <PackageVersion Include="Markdig" Version="1.4.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Systemd" Version="10.0.12" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Systemd" Version="10.0.12" />

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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_<tabel-id>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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

231
SQL Scripts/README.md Normal file
View file

@ -0,0 +1,231 @@
# 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 |
|---|---|
| `<regel-id>.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/<Collector>.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. |
| `_collectors/DatabaseDiscovery.sql`, `_collectors/PermissionCheck.sql` | Søgningen efter databaser (med Dynamics AX-genkendelse) og forbindelsestesten med rettighedstjek under *Servere og databaser* (fra 1.3). De køres kun, når der bedes om dem i webbrugerfladen, og søgningen desuden hver time. |
## 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 `<id>.sql`, og der er ingen filer for ukendte id'er.
- `Scripts_contain_the_current_sql_from_the_code`: hver blok med `-- Kilde: <nøgle>` 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/<id>.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 |

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'%<SystemHealth>%'
) 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'%<SystemHealth>%'
) 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

View file

@ -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

222
SQL Scripts/SQLI-DB-001.sql Normal file
View file

@ -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

223
SQL Scripts/SQLI-DB-002.sql Normal file
View file

@ -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

223
SQL Scripts/SQLI-DB-003.sql Normal file
View file

@ -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

224
SQL Scripts/SQLI-DB-004.sql Normal file
View file

@ -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

223
SQL Scripts/SQLI-DB-005.sql Normal file
View file

@ -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

247
SQL Scripts/SQLI-DB-006.sql Normal file
View file

@ -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

225
SQL Scripts/SQLI-DB-007.sql Normal file
View file

@ -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

225
SQL Scripts/SQLI-DB-008.sql Normal file
View file

@ -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

225
SQL Scripts/SQLI-DB-009.sql Normal file
View file

@ -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

View file

@ -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

223
SQL Scripts/SQLI-DB-011.sql Normal file
View file

@ -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

223
SQL Scripts/SQLI-DB-012.sql Normal file
View file

@ -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

223
SQL Scripts/SQLI-DB-013.sql Normal file
View file

@ -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

224
SQL Scripts/SQLI-DB-014.sql Normal file
View file

@ -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

299
SQL Scripts/SQLI-DB-015.sql Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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'%<SniConsumerError>18456</SniConsumerError>%') rb
CROSS JOIN sys.dm_os_sys_info i
GO

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

116
SQL Scripts/SQLI-QS-001.sql Normal file
View file

@ -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

111
SQL Scripts/SQLI-QS-002.sql Normal file
View file

@ -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

115
SQL Scripts/SQLI-QS-003.sql Normal file
View file

@ -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

View file

@ -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 [<distributionsdatabasen>];" 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 [<distributionsdatabasen>];" 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

View file

@ -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 [<distributionsdatabasen>];" 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 [<distributionsdatabasen>];" 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more