Jag har gett samma rekommendationer om tempdb sedan jag började arbeta med SQL Server för över 15 år sedan, när jag arbetade med kunder som kör version 2000. Kontentan av det:skapa flera datafiler som har samma storlek, med samma auto -tillväxtinställningar, aktivera spårningsflagga 1118 (och kanske 1117) och minska din tempdb-användning. Från kundsidan har detta varit gränsen för vad som kan göras*, fram till SQL Server 2019.
*Det finns några ytterligare kodningsrekommendationer som Pam Lahoud diskuterar i sitt mycket informativa inlägg, TEMPDB – Files and Trace Flags and Updates, Oh My!
Det jag tycker är intressant är att efter all den här tiden är tempdb fortfarande ett problem. SQL Server-teamet har gjort många förändringar under åren för att försöka mildra problem, men missbruket fortsätter. Den senaste anpassningen av SQL Server-teamet flyttar systemtabellerna (metadata) för tempdb till In-Memory OLTP (aka minnesoptimerad). Viss information finns tillgänglig i SQL Server 2019 release notes, och det fanns en demo från Bob Ward och Conor Cunningham under den första dagen av PASS Summit keynote. Pam Lahoud gjorde också en snabb demo i sin allmänna session i PASS Summit. Nu när 2019 CTP 3.2 är ute tänkte jag att det kanske var dags att testa mig själv lite.
Inställningar
Jag har SQL Server 2019 CTP 3.2 installerat på min virtuella maskin, som har 8 GB minne (max serverminne inställt på 6 GB) och 4 vCPU:er. Jag skapade fyra (4) tempdb-datafiler, var och en i storleken 1 GB.
Jag återställde en kopia av WideWorldImporters och skapade sedan tre lagrade procedurer (definitioner nedan). Varje lagrad procedur accepterar en datuminmatning och skickar alla rader från Sales.Order och Sales.OrderLines för det datumet till det tillfälliga objektet. I Sales.usp_OrderInfoTV är objektet en tabellvariabel, i Sales.usp_OrderInfoTT är objektet en temporär tabell definierad via SELECT ... INTO med en icke-klusterad tillagd efteråt, och i Sales.usp_OrderInfoTTALT är objektet en fördefinierad temporär tabell som sedan ändras att ha en extra kolumn. Efter att data har lagts till i det temporära objektet finns det en SELECT-sats mot objektet som ansluter till tabellen Sales.Customers.
/* Create the stored procedures */ USE [WideWorldImporters]; GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTV GO CREATE PROCEDURE Sales.usp_OrderInfoTV @OrderDate DATE AS BEGIN DECLARE @OrdersInfo TABLE ( OrderID INT, OrderLineID INT, CustomerID INT, StockItemID INT, Quantity INT, UnitPrice DECIMAL(18,2), OrderDate DATE); INSERT INTO @OrdersInfo ( OrderID, OrderLineID, CustomerID, StockItemID, Quantity, UnitPrice, OrderDate) SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice, OrderDate FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM @OrdersInfo o JOIN Sales.Customers c ON o.CustomerID = c.CustomerID GROUP BY o.OrderID, c.CustomerName; END GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTT GO CREATE PROCEDURE Sales.usp_OrderInfoTT @OrderDate DATE AS BEGIN SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice, OrderDate INTO #temporderinfo FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM #temporderinfo o JOIN Sales.Customers c ON o.CustomerID = c.CustomerID GROUP BY o.OrderID, c.CustomerName END GO DROP PROCEDURE IF EXISTS Sales.usp_OrderInfoTTALT GO CREATE PROCEDURE Sales.usp_OrderInfoTTALT @OrderDate DATE AS BEGIN CREATE TABLE #temporderinfo ( OrderID INT, OrderLineID INT, CustomerID INT, StockItemID INT, Quantity INT, UnitPrice DECIMAL(18,2)); INSERT INTO #temporderinfo ( OrderID, OrderLineID, CustomerID, StockItemID, Quantity, UnitPrice) SELECT o.OrderID, ol.OrderLineID, o.CustomerID, ol.StockItemID, ol.Quantity, ol.UnitPrice FROM Sales.Orders o INNER JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID WHERE o.OrderDate = @OrderDate; SELECT o.OrderID, c.CustomerName, SUM (o.Quantity), SUM (o.UnitPrice) FROM #temporderinfo o JOIN Sales.Customers c ON o.CustomerID c.CustomerID GROUP BY o.OrderID, c.CustomerName END GO /* Create tables to hold testing data */ USE [WideWorldImporters]; GO CREATE TABLE [dbo].[PerfTesting_Tests] ( [TestID] INT IDENTITY(1,1), [TestName] VARCHAR (200), [TestStartTime] DATETIME2, [TestEndTime] DATETIME2 ) ON [PRIMARY]; GO CREATE TABLE [dbo].[PerfTesting_WaitStats] ( [TestID] [int] NOT NULL, [CaptureDate] [datetime] NOT NULL DEFAULT (sysdatetime()), [WaitType] [nvarchar](60) NOT NULL, [Wait_S] [decimal](16, 2) NULL, [Resource_S] [decimal](16, 2) NULL, [Signal_S] [decimal](16, 2) NULL, [WaitCount] [bigint] NULL, [Percentage] [decimal](5, 2) NULL, [AvgWait_S] [decimal](16, 4) NULL, [AvgRes_S] [decimal](16, 4) NULL, [AvgSig_S] [decimal](16, 4) NULL ) ON [PRIMARY]; GO /* Enable Query Store (testing settings, not exactly what I would recommend for production) */ USE [master]; GO ALTER DATABASE [WideWorldImporters] SET QUERY_STORE = ON; GO ALTER DATABASE [WideWorldImporters] SET QUERY_STORE ( OPERATION_MODE = READ_WRITE, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), DATA_FLUSH_INTERVAL_SECONDS = 600, INTERVAL_LENGTH_MINUTES = 10, MAX_STORAGE_SIZE_MB = 1024, QUERY_CAPTURE_MODE = AUTO, SIZE_BASED_CLEANUP_MODE = AUTO); GO
Test
Standardbeteendet för SQL Server 2019 är att tempdb-metadata inte är minnesoptimerad, och vi kan bekräfta detta genom att kontrollera sys.configurations:
SELECT * FROM sys.configurations WHERE configuration_id = 1589;
För alla tre lagrade procedurer kommer vi att använda sqlcmd för att generera 20 samtidiga trådar som kör en av två olika .sql-filer. Den första .sql-filen, som kommer att användas av 19 trådar, kommer att utföra proceduren i en loop 1000 gånger. Den andra .sql-filen, som bara kommer att ha en (1) tråd, kommer att utföra proceduren i en loop 3000 gånger. Filen inkluderar också TSQL för att fånga två mätvärden av intresse:total varaktighet och väntestatistik. Vi kommer att använda Query Store för att fånga den genomsnittliga varaktigheten för proceduren.
/* Example of first .sql file which calls the SP 1000 times */ SET NOCOUNT ON; GO USE [WideWorldImporters]; GO DECLARE @StartDate DATE; DECLARE @MaxDate DATE; DECLARE @Date DATE; DECLARE @Counter INT = 1; SELECT @StartDATE = MIN(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SELECT @MaxDATE = MAX(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SET @Date = @StartDate; WHILE @Counter <= 1000 BEGIN EXEC [Sales].[usp_OrderInfoTT] @Date; IF @Date <= @MaxDate BEGIN SET @Date = DATEADD(DAY, 1, @Date); END ELSE BEGIN SET @Date = @StartDate; END SET @Counter = @Counter + 1; END GO /* Example of second .sql file which calls the SP 3000 times and captures total duration and wait statisics */ SET NOCOUNT ON; GO USE [WideWorldImporters]; GO DECLARE @StartDate DATE; DECLARE @MaxDate DATE; DECLARE @DATE DATE; DECLARE @Counter INT = 1; DECLARE @TestID INT; DECLARE @TestName VARCHAR(200) = 'Execution of usp_OrderInfoTT - Disk Based System Tables'; INSERT INTO [WideWorldImporters].[dbo].[PerfTesting_Tests] ([TestName]) VALUES (@TestName); SELECT @TestID = MAX(TestID) FROM [WideWorldImporters].[dbo].[PerfTesting_Tests]; SELECT @StartDATE = MIN(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SELECT @MaxDATE = MAX(OrderDate) FROM [WideWorldImporters].[Sales].[Orders]; SET @Date = @StartDate; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1') DROP TABLE [##SQLskillsStats1]; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2') DROP TABLE [##SQLskillsStats2]; SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] INTO ##SQLskillsStats1 FROM sys.dm_os_wait_stats; /* set start time */ UPDATE [WideWorldImporters].[dbo].[PerfTesting_Tests] SET [TestStartTime] = SYSDATETIME() WHERE [TestID] = @TestID; WHILE @Counter <= 3000 BEGIN EXEC [Sales].[usp_OrderInfoTT] @Date; IF @Date <= @MaxDate BEGIN SET @Date = DATEADD(DAY, 1, @Date); END ELSE BEGIN SET @Date = @StartDate; END SET @Counter = @Counter + 1 END /* set end time */ UPDATE [WideWorldImporters].[dbo].[PerfTesting_Tests] SET [TestEndTime] = SYSDATETIME() WHERE [TestID] = @TestID; SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] INTO ##SQLskillsStats2 FROM sys.dm_os_wait_stats; WITH [DiffWaits] AS (SELECT -- Waits that weren't in the first snapshot [ts2].[wait_type], [ts2].[wait_time_ms], [ts2].[signal_wait_time_ms], [ts2].[waiting_tasks_count] FROM [##SQLskillsStats2] AS [ts2] LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] ON [ts2].[wait_type] = [ts1].[wait_type] WHERE [ts1].[wait_type] IS NULL AND [ts2].[wait_time_ms] > 0 UNION SELECT -- Diff of waits in both snapshots [ts2].[wait_type], [ts2].[wait_time_ms] - [ts1].[wait_time_ms] AS [wait_time_ms], [ts2].[signal_wait_time_ms] - [ts1].[signal_wait_time_ms] AS [signal_wait_time_ms], [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] AS [waiting_tasks_count] FROM [##SQLskillsStats2] AS [ts2] LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] ON [ts2].[wait_type] = [ts1].[wait_type] WHERE [ts1].[wait_type] IS NOT NULL AND [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] > 0 AND [ts2].[wait_time_ms] - [ts1].[wait_time_ms] > 0), [Waits] AS (SELECT [wait_type], [wait_time_ms] / 1000.0 AS [WaitS], ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS], [signal_wait_time_ms] / 1000.0 AS [SignalS], [waiting_tasks_count] AS [WaitCount], 100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage], ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum] FROM [DiffWaits] WHERE [wait_type] NOT IN ( -- These wait types are almost 100% never a problem and so they are -- filtered out to avoid them skewing the results. N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', N'CXCONSUMER', N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST', N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK', N'PREEMPTIVE_XE_GETTARGETSTATE', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE', N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'QDS_SHUTDOWN_QUEUE', N'REDO_THREAD_PENDING_WORK', N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_RECOVERY', N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'XE_DISPATCHER_JOIN', N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT' ) ) INSERT INTO [WideWorldImporters].[dbo].[PerfTesting_WaitStats] ( [TestID], [WaitType] , [Wait_S] , [Resource_S] , [Signal_S] , [WaitCount] , [Percentage] , [AvgWait_S] , [AvgRes_S] , [AvgSig_S] ) SELECT @TestID, [W1].[wait_type] AS [WaitType], CAST ([W1].[WaitS] AS DECIMAL (16, 2)) AS [Wait_S], CAST ([W1].[ResourceS] AS DECIMAL (16, 2)) AS [Resource_S], CAST ([W1].[SignalS] AS DECIMAL (16, 2)) AS [Signal_S], [W1].[WaitCount] AS [WaitCount], CAST ([W1].[Percentage] AS DECIMAL (5, 2)) AS [Percentage], CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgWait_S], CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgRes_S], CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgSig_S] FROM [Waits] AS [W1] INNER JOIN [Waits] AS [W2] ON [W2].[RowNum] <= [W1].[RowNum] GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], [W1].[ResourceS], [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage] HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95; -- percentage threshold GO -- Cleanup IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1') DROP TABLE [##SQLskillsStats1]; IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2') DROP TABLE [##SQLskillsStats2]; GO
Exempel på kommandoradsfil:
Resultat
Efter att ha kört kommandoradsfilerna som genererar 20 trådar för varje lagrad procedur, kontrollerar den totala varaktigheten för de 12 000 körningarna av varje procedur följande:
SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration] FROM [dbo].[PerfTesting_Tests] ORDER BY [TestID];
De lagrade procedurerna med de temporära tabellerna (usp_OrderInfoTT och usp_OrderInfoTTC) tog längre tid att slutföra. Om vi tittar på individuella frågeresultat:
SELECT [qsq].[query_id], [qsp].[plan_id], OBJECT_NAME([qsq].[object_id]) AS [ObjectName], [rs].[count_executions], [rs].[last_execution_time], [rs].[avg_duration], [rs].[avg_logical_io_reads], [qst].[query_sql_text] FROM [sys].[query_store_query] [qsq] JOIN [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] = [qst].[query_text_id] JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] = [qsp].[query_id] JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] = [rs].[plan_id] WHERE ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTT')) OR ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTV')) OR ([qsq].[object_id] = OBJECT_ID('Sales.usp_OrderInfoTTALT')) ORDER BY [qsq].[query_id], [rs].[last_execution_time];
Vi kan se att SELECT ... INTO för usp_OrderInfoTT tog cirka 28 ms i genomsnitt (varaktigheten i Query Store lagras i mikrosekunder), och bara tog 9 ms när den temporära tabellen var förskapad. För tabellvariabeln tog INSERT drygt 22ms i genomsnitt. Intressant nog tog SELECT-frågan drygt 1 ms för de temporära tabellerna och ungefär 2,7 ms för tabellvariabeln.
En kontroll av väntestatistikdata hittar en bekant wait_type, PAGELATCH*:
SELECT * FROM [dbo].[PerfTesting_WaitStats] ORDER BY [TestID], [Percentage] DESC;
Lägg märke till att vi bara ser PAGELATCH* väntar på test 1 och 2, som var procedurerna med de temporära tabellerna. För usp_OrderInfoTV, som använde en tabellvariabel, ser vi bara SOS_SCHEDULER_YIELD-väntningar. Observera: Detta innebär inte på något sätt att du ska använda tabellvariabler istället för temporära tabeller , och det betyder inte heller att du inte kommer att göra det har PAGELATCH väntar med tabellvariabler. Detta är ett konstruerat scenario; Jag mycket rekommenderar att du testar med DIN kod för att se vilka wait_types som visas.
Nu kommer vi att ändra instansen för att använda minnesoptimerade tabeller för tempdb-metadata. Det finns två sätt detta kan göras, via kommandot ALTER SERVER CONFIGURATION, eller genom att använda sp_configure. Eftersom den här inställningen är ett avancerat alternativ, om du använder sp_configure måste du först aktivera avancerade alternativ.
ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; GO
Efter denna ändring är det nödvändigt att starta om till instansen. (OBS:du kan ändra tillbaka detta till att INTE använda minnesoptimerade tabeller, du måste bara starta om instansen igen.) Efter omstarten, om vi kontrollerar sys.configurations igen kan vi se att metadatatabellerna är minnesoptimerade:
Efter att ha kört kommandoradsfilerna igen visar den totala varaktigheten för de 21 000 körningarna av varje procedur följande (observera att resultaten är ordnade efter lagrad procedur för enklare jämförelse):
Det var definitivt en förbättring av prestanda för både usp_OrderInfoTT och usp_OrderInfoTTC , och en liten ökning av prestanda för usp_OrderInfoTV. Låt oss kontrollera sökfrågans varaktighet:
För alla frågor är frågans varaktighet nästan densamma, förutom ökningen av INSERT-varaktigheten när tabellen är förskapad, vilket är helt oväntat. Vi ser en intressant förändring i väntestatistiken:
För usp_OrderInfoTT exekveras en SELECT … INTO för att skapa den temporära tabellen. Väntetiden ändras från att vara PAGELATCH_EX och PAGELATCH_SH till endast PAGELATCH_EX och SOS_SCHEDULER_YIELD. Vi ser inte längre PAGELATCH_SH-väntningarna.
För usp_OrderInfoTTC, som skapar den temporära tabellen och sedan infogar, visas inte längre PAGELATCH_EX- och PAGELATCH_SH-väntningarna, och vi ser bara SOS_SCHEDULER_YIELD-väntningar.
Slutligen, för OrderInfoTV är väntetiderna konsekventa – bara SOS_SCHEDULER_YIELD, med nästan samma totala väntetid.
Sammanfattning
Baserat på denna testning ser vi en förbättring i alla fall, avsevärt för de lagrade procedurerna med temporära tabeller. Det finns en liten förändring för tabellvariabelproceduren. Det är extremt viktigt att komma ihåg att detta är ett scenario, med ett litet belastningstest. Jag var väldigt intresserad av att prova dessa tre mycket enkla scenarier, för att försöka förstå vad som skulle kunna dra mest nytta av att göra tempdb-metadataminnet optimerat. Denna arbetsbelastning var liten och pågick under en mycket begränsad tid – jag hade faktiskt mer varierande resultat med fler trådar, vilket är värt att utforska i ett annat inlägg. Den största fördelen är att, som med alla nya funktioner och funktionalitet, är testning viktigt. För den här funktionen vill du ha en baslinje för nuvarande prestanda mot vilken du kan jämföra mätvärden som Batch Requests/Sec och väntestatistik efter att ha gjort metadataminnet optimerat.
Ytterligare överväganden
Att använda OLTP i minnet kräver en filgrupp av typen MEMORY OPTIMIZED DATA. Men efter att ha aktiverat MEMORY_OPTIMIZED TEMPDB_METADATA skapas ingen ytterligare filgrupp för tempdb. Dessutom är det inte känt om de minnesoptimerade tabellerna är hållbara (SCHEMA_AND_DATA) eller inte (SCHEMA_ONLY). Vanligtvis kan detta bestämmas via sys.tables (durability_desc), men ingenting returneras för de inblandade systemtabellerna när du frågar efter detta i tempdb, även när du använder den dedikerade administratörsanslutningen. Du har möjlighet att visa icke-klustrade index för de minnesoptimerade tabellerna. Du kan använda följande fråga för att se vilka tabeller som är minnesoptimerade i tempdb:
SELECT * FROM tempdb.sys.dm_db_xtp_object_stats x JOIN tempdb.sys.objects o ON x.object_id = o.object_id JOIN tempdb.sys.schemas s ON o.schema_id = s.schema_id;
Kör sedan sp_helpindex för någon av tabellerna, till exempel:
EXEC sys.sp_helpindex N'sys.sysobjvalues';
Observera att om det är ett hashindex (vilket kräver att man uppskattar BUCKET_COUNT som en del av skapandet), skulle beskrivningen inkludera "icke-klustrad hash."