AxSql.Reset ran SET LOCK_TIMEOUT -1 and READ COMMITTED after every AX read (and after the AX detection in discovery). The collectors of an instance share one connection, so every collector after the AX ones waited on locks without limit instead of the agent's 5 s, and no longer read uncommitted. Reset now restores the agent's own settings, from one place (Core.Instances.AgentSession) that SqlConnectionFactory.SessionSettings also uses. The AX reads themselves are unchanged: READ COMMITTED (or SNAPSHOT) with a 2 s lock timeout inside sp_executesql, never NOLOCK. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
168 lines
12 KiB
Transact-SQL
168 lines
12 KiB
Transact-SQL
-- ===================================================================================================================
|
||
-- SQLI-AX-010: AX-bruger blokerer andre
|
||
-- ===================================================================================================================
|
||
-- Regel-id: SQLI-AX-010
|
||
-- Titel: AX-bruger blokerer andre
|
||
-- Alvor: Medium; High ved ventetid over 5 minutter eller mindst 10 blokerede sessioner.
|
||
-- Versioner: Blokering: SQL Server 2000 og nyere; AX-genkendelsen og dermed reglen: SQL Server 2005 og nyere.
|
||
-- Axapta 3.0/4.0: AX-brugeren kan ikke ses. AX 2009/2012/F&O: baseret på Microsofts dokumentation - ikke
|
||
-- verificeret mod kundedata.
|
||
--
|
||
-- Hvad tjekkes, og hvorfor:
|
||
-- En session i en Dynamics AX-database holder låse, som andre AX-brugere eller batchjob venter på. Alle AX-brugere går
|
||
-- gennem AOS'ens login, så SQL Server-login'et siger ikke, hvem det er. Reglen viser AX-brugeren, når SQL Server kan
|
||
-- se den.
|
||
--
|
||
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
|
||
-- For hver head blocker, hvis egen database eller en blokeret sessions database er genkendt som AX: fund, når den
|
||
-- længste ventetid i kæden er over grænsen. AX 2009/2012/F&O: AX-brugeren og AX-sessionen læses fra CONTEXT_INFO,
|
||
-- som AOS'en sætter, når "connectioncontext" er slået til i AOS-konfigurationen (formatet er dokumenteret af
|
||
-- Microsoft for AX 2012 - ikke verificeret mod kundedata). Axapta 3.0 og AX 4.0 sætter aldrig CONTEXT_INFO:
|
||
-- brugeren kan ikke ses fra SQL Server, og fundet siger det ærligt. AX-bruger, vært, program og CONTEXT_INFO er
|
||
-- følsomme målinger (sendes ikke til SQLInsight Hub uden SendSensitiveEvidence). Brugeren findes af
|
||
-- src/SQLInsight.Core/DynamicsAx/AxSessionMapper.cs. AX-tabeller læses ikke (og aldrig med NOLOCK).
|
||
--
|
||
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
|
||
-- SQLInsight:DynamicsAx:BlockingSeconds = 30: den længste ventetid bag sessionen skal være mindst så mange sekunder.
|
||
-- High fra 300.000 ms eller 10 blokerede (fast i koden, som SQLI-BLK-001).
|
||
--
|
||
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql og grant-dynamics-ax-read.sql):
|
||
-- Ingen AX-læseadgang: kun grant-least-privilege.sql.
|
||
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019 (sessionerne)
|
||
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+
|
||
-- GRANT VIEW ANY DEFINITION TO [agent]; -- AX-genkendelsen (metadata)
|
||
-- GRANT CONNECT ANY DATABASE TO [agent]; -- SQL Server 2014+; før 2014 en databasebruger i AX-databasen
|
||
--
|
||
-- Data kommer fra (collector, fil : medlemmer, interval):
|
||
-- AxDatabaseDetector (den fælles AX-genkendelse)
|
||
-- Fil: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs
|
||
-- Medlemmer: AxDatabaseDetector.DatabasesSql, AxDatabaseDetector.BuildDetectionSql(database)
|
||
-- Interval: hver time pr. database (hvert 5. minut, mens AX-læseadgangen mangler), delt af alle AX-collectors
|
||
-- Delt script: SQL Scripts/_collectors/AxDatabaseDetector.sql
|
||
-- 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/DynamicsAx/DynamicsAxRules.cs : AxBlockingUserRule
|
||
--
|
||
-- Read-only: Ja: kun SELECT med TOP og SET-sessionsindstillinger. AX-tabellerne læses i AX-databasen via
|
||
-- [db].sys.sp_executesql (sessionen skifter aldrig database) under READ COMMITTED med SET LOCK_TIMEOUT 2000
|
||
-- (SNAPSHOT, når databasen tillader det), aldrig NOLOCK; bagefter sættes agentens sessionsindstillinger
|
||
-- igen. Intet ændrer data, indstillinger eller objekter. Kun i databaser genkendt som AX og valgt til
|
||
-- overvågning.
|
||
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
|
||
-- (GO); varianter for andre AX-versioner end din fejler eller giver ingen rækker - det er forventet.
|
||
-- AX-forespørgslerne er vist for databasen AXDB: ret [AXDB] til din AX-database.
|
||
-- ===================================================================================================================
|
||
|
||
-- -------------------------------------------------------------------------------------------------------------------
|
||
-- 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
|
||
|
||
-- -------------------------------------------------------------------------------------------------------------------
|
||
-- AxDatabaseDetector: Hvilke overvågede databaser er Dynamics AX (kun metadata)
|
||
-- -------------------------------------------------------------------------------------------------------------------
|
||
|
||
-- Variant: Alle versioner fra SQL Server 2005: online brugerdatabaser og deres snapshot-indstillinger
|
||
-- Kilde: AxDatabaseDetector.DatabasesSql
|
||
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.DatabasesSql
|
||
SELECT name, snapshot_isolation_state, is_read_committed_snapshot_on
|
||
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: AX-genkendelsen i hver overvåget database (kun metadata; vist for AXDB)
|
||
-- Kilde: AxDatabaseDetector.BuildDetectionSql
|
||
-- Kode: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs : AxDatabaseDetector.BuildDetectionSql(database) = AxSql.InDatabase(database, AxDetection.MetadataSql)
|
||
-- Agenten kører dette i hver overvåget database fra listen ovenfor (ikke *_model). Ret [AXDB] til den database, du vil tjekke.
|
||
EXEC [AXDB].sys.sp_executesql N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT s.name, UPPER(t.name), UPPER(c.name), TYPE_NAME(c.system_type_id), c.precision, c.max_length,
|
||
ISNULL(HAS_PERMS_BY_NAME(QUOTENAME(s.name) + N''.'' + QUOTENAME(t.name), N''OBJECT'', N''SELECT''), 0),
|
||
(SELECT TOP 1 p.name FROM sys.database_principals p WHERE p.sid = SUSER_SID())
|
||
FROM sys.tables t
|
||
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||
JOIN sys.columns c ON c.object_id = t.object_id
|
||
WHERE UPPER(t.name) IN (N''SQLDICTIONARY'', N''DATAAREA'', N''SYSCONFIG'', N''BATCH'', N''BATCHJOB'', N''BATCHJOBHISTORY'', N''BATCHGROUP'', N''NUMBERSEQUENCETABLE'', N''NUMBERSEQUENCESCOPE'', N''NUMBERSEQUENCEREFERENCE'', N''SYSTEMSEQUENCES'', N''MODELELEMENT'')
|
||
ORDER BY s.name, t.name, c.column_id';
|
||
SET LOCK_TIMEOUT 5000; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
||
GO
|
||
|
||
-- -------------------------------------------------------------------------------------------------------------------
|
||
-- BlockingCollector: Blokerede sessioner og deres blokerere lige nu, med sætning, åbne transaktioner og CONTEXT_INFO.
|
||
-- -------------------------------------------------------------------------------------------------------------------
|
||
|
||
-- 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, s.context_info
|
||
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 2008 – 2008 R2
|
||
-- Kilde: BlockingCollector.BuildSql2005(2008)
|
||
-- 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, s.context_info
|
||
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 (CONTEXT_INFO fra sys.sysprocesses)
|
||
-- 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, (SELECT TOP 1 p.context_info FROM sys.sysprocesses p WHERE p.spid = s.session_id AND p.ecid = 0)
|
||
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, p.context_info
|
||
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
|
||
|