Community rules for Microsoft Dynamics AX, with emphasis on AX 3.0
Some checks are pending
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
CI / Build og unit tests (push) Successful in 1m3s
deploy community / deploy (push) Successful in 13s
CI / MSI-installer (push) Successful in 36s
Some checks are pending
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
CI / Build og unit tests (push) Successful in 1m3s
deploy community / deploy (push) Successful in 13s
CI / MSI-installer (push) Successful in 36s
COM-AX-001..007, Database scope, metadata and DMVs only (the agent has no data access). An AX database is recognised by OBJECT_ID() of SQLDICTIONARY, DATAAREA and SYSCONFIG, AX 3.0 by its 32-bit RECID, so the queries compile and return nothing in every other database. SQL Server 2005 syntax. - AX 3.0 out of support; AX 3.0 on SQL Server newer than 2005 - MAXDOP not 1 (server value before 2016, database scoped from 2016) - tables without a unique RECID index (failed or partial synchronisation) - large heaps (ClusterIndex not set in the AOT), statistics off Tested against a mock AX 3.0 database and a plain database on SQL Server 2025, with QUOTED_IDENTIFIER both on and off (no XML methods). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3bfb59f302
commit
e1d45e1734
1 changed files with 108 additions and 0 deletions
108
rules/community/dynamics-ax.json
Normal file
108
rules/community/dynamics-ax.json
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Microsoft Dynamics AX (Axapta), with emphasis on AX 3.0.
|
||||||
|
//
|
||||||
|
// The agent has no data access (grant-least-privilege.sql), so these rules only read metadata and DMVs. They run in
|
||||||
|
// every user database, so an AX database is recognised without naming its tables directly (that would fail to compile
|
||||||
|
// elsewhere): OBJECT_ID() of the AX system tables SQLDICTIONARY, DATAAREA and SYSCONFIG. AX 3.0 is told apart from
|
||||||
|
// AX 4.0 and later by RECID being 32-bit (int) instead of 64-bit (bigint).
|
||||||
|
//
|
||||||
|
// Everything is SQL Server 2005 syntax (no CONCAT, IIF or STRING_AGG): AX 3.0 typically runs on 2000 or 2005.
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "COM-AX-001",
|
||||||
|
"title": "Dynamics AX 3.0 er uden support fra Microsoft",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS [sql_version] WHERE OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL AND COLUMNPROPERTY(OBJECT_ID(N'dbo.SQLDICTIONARY'), N'RECID', 'Precision') = 10",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "{database} er en Dynamics AX 3.0-database, og AX 3.0 er uden support",
|
||||||
|
"explanation": "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.",
|
||||||
|
"recommendation": "Planlæg en opgradering eller migrering væk fra AX 3.0. Indtil da: tag fuld backup og afprøv gendannelse jævnligt, hold AX-serverne adskilt fra internettet, og ændr ikke Windows, SQL Server eller compatibility level uden en fuld test i et testmiljø.",
|
||||||
|
"evidence": [ "sql_version" ],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-002",
|
||||||
|
"title": "Dynamics AX 3.0 kører på en SQL Server-version, der ikke var understøttet til AX 3.0",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL AND COLUMNPROPERTY(OBJECT_ID(N'dbo.SQLDICTIONARY'), N'RECID', 'Precision') = 10",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "AX 3.0-databasen {database} ligger på SQL Server {sql_version}",
|
||||||
|
"explanation": "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}.",
|
||||||
|
"recommendation": "Er det en produktionsdatabase, så dokumentér at kombinationen er en bevidst beslutning, og test hver opdatering af SQL Server (CU) i et testmiljø med en kopi af databasen, før den rulles ud. Hæv ikke compatibility level uden en test af de tunge kørsler (lageropgørelse, finansafslutning, batchjob). Er det en kopi (arkiv eller test), så sørg for, at ingen AOS bruger den ved en fejl.",
|
||||||
|
"evidence": [ "sql_version", "compatibility_level" ],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-003",
|
||||||
|
"title": "Dynamics AX-database på en instans, hvor MAXDOP ikke er 1 (SQL Server 2005-2014)",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "MAXDOP er {maxdop} på instansen med Dynamics AX-databasen {database}",
|
||||||
|
"explanation": "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.",
|
||||||
|
"recommendation": "Sæt MAXDOP til 1: EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'max degree of parallelism', 1; RECONFIGURE; Ændringen virker med det samme og kræver ingen genstart. Deler instansen med systemer, der har gavn af parallelisme (fx rapportering), så flyt AX til egen instans, eller brug OPTION (MAXDOP n) i de tunge rapporter.",
|
||||||
|
"evidence": [ "maxdop" ],
|
||||||
|
"references": [
|
||||||
|
{ "title": "Configure the max degree of parallelism", "url": "https://learn.microsoft.com/sql/database-engine/configure-windows/configure-the-max-degree-of-parallelism-server-configuration-option" }
|
||||||
|
],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-004",
|
||||||
|
"title": "Dynamics AX-database, hvor MAXDOP ikke er 1 (SQL Server 2016 og nyere)",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"minimumVersion": "13.0",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "Dynamics AX-databasen {database} kører med MAXDOP større end 1",
|
||||||
|
"explanation": "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).",
|
||||||
|
"recommendation": "Sæt MAXDOP 1 kun for AX-databasen, så andre databaser på instansen ikke påvirkes: USE [{database}]; ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 1; Det virker med det samme og kræver ingen genstart.",
|
||||||
|
"evidence": [ "server_maxdop", "database_maxdop" ],
|
||||||
|
"references": [
|
||||||
|
{ "title": "ALTER DATABASE SCOPED CONFIGURATION", "url": "https://learn.microsoft.com/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql" }
|
||||||
|
],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-005",
|
||||||
|
"title": "Dynamics AX-tabeller uden unikt RecId-indeks",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL HAVING COUNT(*) > 0",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "{tables} AX-tabeller i {database} har intet unikt indeks på RECID",
|
||||||
|
"explanation": "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}.",
|
||||||
|
"recommendation": "Kør en synkronisering af tabeller og indekser fra AX (Administration → Periodisk → SQL-administration), først i et testmiljø, og læs synkroniseringsloggen for fejl. Opret ikke indekserne direkte i SQL Server: AX kender dem så ikke og kan fjerne dem ved næste synkronisering.",
|
||||||
|
"evidence": [ "tables", "examples" ],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-006",
|
||||||
|
"title": "Store Dynamics AX-tabeller uden clustered index (heaps)",
|
||||||
|
"severity": "Medium",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL HAVING COUNT(*) > 0",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "{tables} store AX-tabeller i {database} har intet clustered index",
|
||||||
|
"explanation": "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}.",
|
||||||
|
"recommendation": "Sæt ClusterIndex på tabellen i AOT (typisk det indeks, der oftest søges på, eller RecId-indekset) og synkronisér, først i et testmiljø og uden for arbejdstid, da tabellen bygges om. Opret ikke clustered index direkte i SQL Server: AX kender det så ikke og kan ændre det ved næste synkronisering.",
|
||||||
|
"evidence": [ "tables", "rows", "largest" ],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "COM-AX-007",
|
||||||
|
"title": "Automatiske statistikker er slået fra i en Dynamics AX-database",
|
||||||
|
"severity": "High",
|
||||||
|
"scope": "Database",
|
||||||
|
"query": "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 OBJECT_ID(N'dbo.SQLDICTIONARY', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.DATAAREA', N'U') IS NOT NULL AND OBJECT_ID(N'dbo.SYSCONFIG', N'U') IS NOT NULL",
|
||||||
|
"subject": "database: {database}",
|
||||||
|
"findingTitle": "Automatiske statistikker er slået fra i AX-databasen {database}",
|
||||||
|
"explanation": "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}.",
|
||||||
|
"recommendation": "Slå begge til: ALTER DATABASE [{database}] SET AUTO_CREATE_STATISTICS ON; ALTER DATABASE [{database}] SET AUTO_UPDATE_STATISTICS ON; Behold et natligt vedligeholdelsesjob, der opdaterer statistikker på de største tabeller.",
|
||||||
|
"evidence": [ "auto_create_statistics", "auto_update_statistics" ],
|
||||||
|
"author": "SQLInsight Pro"
|
||||||
|
}
|
||||||
|
]
|
||||||
Loading…
Add table
Reference in a new issue