Batch jobs: failed (001), longer than their own history (002), late or never started (003), stuck in Executing (004). Number sequences: running out with a forecast from SQLInsight's own samples (005), format shorter than HIGHEST (006), blocked but still referenced (007), continuous and not cleaned up (008). RecId running out, flagged far earlier for AX 3.0's 32-bit RecId (009). AX user behind blocking from CONTEXT_INFO, honest where AX 3.0 cannot tell (010). Subjects carry the database, so findings follow the hub opt-in. SQL Scripts/<rule>.sql (and _collectors/) for every AX rule added or changed in 1.4, incl. COM-AX-001..007: a Danish header plus the exact SQL SQLInsight sends, generated from the code and checked by SqlScriptTests (rewrite with SQLINSIGHT_UPDATE_SQL_SCRIPTS=1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
185 lines
12 KiB
Transact-SQL
185 lines
12 KiB
Transact-SQL
/*
|
||
Regel: SQLI-AX-010 – AX-bruger blokerer andre
|
||
Alvorlighed: Medium; Høj (High) ved ventetid over 5 minutter eller mindst 10 blokerede sessioner
|
||
|
||
Hvad og hvorfor
|
||
En session fra Dynamics AX 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.
|
||
|
||
Tærskel (agentens konfiguration SQLInsight:DynamicsAx)
|
||
BlockingSeconds = 30 den længste ventetid bag sessionen skal være mindst så mange sekunder
|
||
|
||
Vurdering (C#: src/SQLInsight.Rules/DynamicsAx/DynamicsAxRules.cs – AxBlockingUserRule; brugeren findes af
|
||
src/SQLInsight.Core/DynamicsAx/AxSessionMapper.cs)
|
||
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 tærsklen. 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 (første ord med bogstaver = bruger, første
|
||
tal = AX-session; 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 og viser vært
|
||
(AOS'en ved 3-lags, brugerens pc ved 2-lags-klient) og program. AX-brugeren er følsom (sendes ikke til hubben uden
|
||
tilladelse).
|
||
|
||
Collector: src/SQLInsight.Collectors/DynamicsAx/AxDatabaseDetector.cs – AxDatabaseDetector.GetAsync (via DynamicsAxCollector,
|
||
hver time) og src/SQLInsight.Collectors/Concurrency/BlockingCollector.cs – BuildSql2005/Sql2000 (hvert minut).
|
||
Rettigheder: Ingen ud over grant-least-privilege.sql (VIEW SERVER STATE til sessionerne, VIEW ANY DEFINITION til
|
||
genkendelsen). Ingen AX-læseadgang nødvendig.
|
||
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.
|
||
Læsning: Kun DMV'er og metadata. Genkendelsen kører i hver overvåget database via [db].sys.sp_executesql.
|
||
*/
|
||
-- >>> SQL, som SQLInsight kører – genereret fra koden (tests/SQLInsight.Collectors.Tests/SqlScriptTests.cs). Ret ikke herunder i hånden.
|
||
-- Variant: Alle versioner: online brugerdatabaser og deres snapshot-indstillinger (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: Alle versioner: agentens login, til GRANT-teksten
|
||
SELECT SUSER_SNAME()
|
||
GO
|
||
|
||
-- Variant: SQL Server 2005 og nyere: AX-genkendelse i hver overvåget database (AxDetection.MetadataSql via AxSql.InDatabase)
|
||
DECLARE @Database sysname;
|
||
SET @Database = N'AXDB'; -- <== ret til databasen
|
||
DECLARE @Sql nvarchar(max);
|
||
SET @Sql = 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';
|
||
DECLARE @Procedure nvarchar(300);
|
||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
||
EXEC @Procedure @Sql;
|
||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
GO
|
||
|
||
-- Variant: SQL Server 2000 (kun opdagelse under Servere og databaser, AxDetection.MetadataSql2000)
|
||
SELECT u.name, UPPER(o.name), UPPER(c.name), TYPE_NAME(c.xtype), c.xprec, c.length, 0, NULL
|
||
FROM [AXDB].dbo.sysobjects o
|
||
JOIN [AXDB].dbo.sysusers u ON u.uid = o.uid
|
||
JOIN [AXDB].dbo.syscolumns c ON c.id = o.id
|
||
WHERE o.xtype = 'U' AND UPPER(o.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 u.name, o.name, c.colid
|
||
GO
|
||
|
||
-- Variant: Axapta 3.0 (skema bmssa): firmaer (AxSchemaAdapter.Companies)
|
||
DECLARE @Database sysname;
|
||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
||
DECLARE @Schema sysname;
|
||
SET @Schema = N'bmssa'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
||
DECLARE @Sql nvarchar(max);
|
||
SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [bmssa].[DATAAREA] d ORDER BY d.[ID]';
|
||
SET @Sql = REPLACE(@Sql, N'[bmssa].', QUOTENAME(@Schema) + N'.');
|
||
DECLARE @Procedure nvarchar(300);
|
||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
||
EXEC @Procedure @Sql;
|
||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
GO
|
||
|
||
-- Variant: AX 4.0 (skema dbo) – ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
||
DECLARE @Database sysname;
|
||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
||
DECLARE @Schema sysname;
|
||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
||
DECLARE @Sql nvarchar(max);
|
||
SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]';
|
||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
||
DECLARE @Procedure nvarchar(300);
|
||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
||
EXEC @Procedure @Sql;
|
||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
GO
|
||
|
||
-- Variant: AX 2009 – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
||
DECLARE @Database sysname;
|
||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
||
DECLARE @Schema sysname;
|
||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
||
DECLARE @Sql nvarchar(max);
|
||
SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]';
|
||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
||
DECLARE @Procedure nvarchar(300);
|
||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
||
EXEC @Procedure @Sql;
|
||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
GO
|
||
|
||
-- Variant: AX 2012 RTM/R2/R3 og F&O – baseret på Microsofts dokumenterede skema, ikke verificeret mod kundedata: firmaer (AxSchemaAdapter.Companies)
|
||
DECLARE @Database sysname;
|
||
SET @Database = N'AXDB'; -- <== ret til AX-databasen
|
||
DECLARE @Schema sysname;
|
||
SET @Schema = N'dbo'; -- <== ejeren af AX-tabellerne: bmssa i Axapta 3.0, ellers dbo
|
||
DECLARE @Sql nvarchar(max);
|
||
SET @Sql = N'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT TOP (500) d.[ID], d.[NAME], d.[ISVIRTUAL] FROM [dbo].[DATAAREA] d ORDER BY d.[ID]';
|
||
SET @Sql = REPLACE(@Sql, N'[dbo].', QUOTENAME(@Schema) + N'.');
|
||
DECLARE @Procedure nvarchar(300);
|
||
SET @Procedure = QUOTENAME(@Database) + N'.sys.sp_executesql';
|
||
EXEC @Procedure @Sql;
|
||
SET LOCK_TIMEOUT -1; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||
GO
|
||
|
||
-- Variant: SQL Server 2012 og nyere (BlockingCollector.BuildSql2005)
|
||
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 og 2008 R2
|
||
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
|
||
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 (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
|
||
-- <<< slut på genereret SQL
|