/* 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