Recently we had an AOS (AX 2012 R3) which took more than 20 minutes to shut down while it normally should take a minute or two. When checking SQL we saw that the AOS was dropping a large number of tempdb tables (about 450000).
While we do have a reasonably large database and do millions of transactions each day, having almost half a million tempdb tables seems a bit excessive.
So, where do these temp tables come from?
Looking at the tables in tempdb reveals the following:
- they start with t
- then a number
- followed by an _
- and a guid like string
e.g. t7461_15722EEBE3404D5B80A6056D701084FD
After comparing some table schemas we determined that the number between the “t” and the “_” is actually the AX table ID. Cool, so now we can run the following query to to get some more insight on the tables used (replace development_model with your model database name)
select ttables.name, cast(SUBSTRING(ttables.name, 2, CHARINDEX('_', ttables.name) - 2) as int) tableid, me.name from tempdb.sys.tables ttables with (nolock) left join development_model.dbo.ModelElement me on me.AxId = cast(SUBSTRING(ttables.name, 2, CHARINDEX('_', ttables.name) - 2) as int) and me.ElementType = 44 where ttables.name like 't%[_]%' order by ttables.create_date desc
Some tables have multiple tempdb copies, use the following query to find the most frequent occurrence of a table in tempdb (replace development_model with your model database name).
select me.name, count(1) as occurrences from tempdb.sys.tables ttables with (nolock) left join development_model.dbo.ModelElement me on me.AxId = cast(SUBSTRING(ttables.name, 2, CHARINDEX('_', ttables.name) - 2) as int) and me.ElementType = 44 where ttables.name like 't%[_]%' group by me.name order by count(1) desc
Now, why did AX create 2710 temp table copies of the same table?
Lets see what happens when we run a job that selects a single record from the RAssetPurchTrans table (after dropping all tempdb tables).
static void testTempdb(Args _args) { boolean testTempdb; testTempdb = (select firstonly RAssetPurchTrans).RecId != 0; info(strFmt("test is %1", testTempdb)); }
What if we run that job a second time? Will it create another tempdb table?
What if we run it 10 times in a for loop?
static void testTempdb(Args _args) { boolean testTempdb; int i; for (i = 1; i <= 10; i++) { testTempdb = (select firstonly RAssetPurchTrans).RecId != 0; } info(strFmt("test is %1", testTempdb)); }
Well, what if we run that job within a transaction?
static void testTempdb(Args _args) { boolean testTempdb; int i; ttsBegin; for (i = 1; i <= 10; i++) { testTempdb = (select firstonly RAssetPurchTrans).RecId != 0; } ttsCommit; info(strFmt("test is %1", testTempdb)); }
So, if we’re running inside a transaction AX will create a new tempdb table each time we select a record from the RAssetPurchTrans table.
But why is a tempdb table even created?
While testing this with a new table I created myself, no tempdb tables were created at all. Well, the table I created doesn’t have a configuration key associated with it, while the RAssetPurchTrans table has the RAsset configuration key associated which is disabled.
According to Microsoft docs this is normal/expected behaviour
Avoiding / solving the issue
Since we’re running a query on a table that is disabled, we might as well check whether the configuration key is enabled so we can skip the actual query.
static void testTempdb(Args _args) { boolean testTempdb; int i; ttsBegin; for (i = 1; i <= 10; i++) { if (isConfigurationKeyEnabled(configurationkeynum(RAsset))) { testTempdb = (select firstonly RAssetPurchTrans).RecId != 0; } } ttsCommit; info(strFmt("test is %1", testTempdb)); }
By doing it this way no tempdb table will be created for RAssetPurchTrans.