Jeg har givet de samme anbefalinger om tempdb, siden jeg begyndte at arbejde med SQL Server for over 15 år siden, da jeg arbejdede med kunder, der kørte version 2000. Essensen af det:opret flere datafiler, der har samme størrelse, med den samme auto -Vækstindstillinger, aktiver sporingsflag 1118 (og måske 1117), og reducer dit tempdb-brug. Fra kundesiden har dette været grænsen for, hvad der kan lade sig gøre*, indtil SQL Server 2019.
*Der er et par yderligere kodningsanbefalinger, som Pam Lahoud diskuterer i sit meget informative indlæg, TEMPDB – Files and Trace Flags and Updates, Oh My!
Det, jeg finder interessant, er, at efter al denne tid er tempdb stadig et problem. SQL Server-teamet har foretaget mange ændringer gennem årene for at forsøge at afbøde problemer, men misbruget fortsætter. Den seneste tilpasning fra SQL Server-teamet flytter systemtabellerne (metadata) for tempdb til In-Memory OLTP (alias hukommelsesoptimeret). Nogle oplysninger er tilgængelige i SQL Server 2019 release notes, og der var en demo fra Bob Ward og Conor Cunningham under den første dag af PASS Summit keynote. Pam Lahoud lavede også en hurtig demo i hendes PASS Summit general session. Nu hvor 2019 CTP 3.2 er ude, tænkte jeg, at det måske var på tide at teste mig selv lidt.
Opsætning
Jeg har SQL Server 2019 CTP 3.2 installeret på min virtuelle maskine, som har 8 GB hukommelse (maks. serverhukommelse indstillet til 6 GB) og 4 vCPU'er. Jeg oprettede fire (4) tempdb-datafiler, hver størrelse til 1 GB.
Jeg gendannede en kopi af WideWorldImporters og oprettede derefter tre lagrede procedurer (definitioner nedenfor). Hver lagret procedure accepterer en datoinput og skubber alle rækker fra Sales.Order og Sales.OrderLines for den pågældende dato ind i det midlertidige objekt. I Sales.usp_OrderInfoTV er objektet en tabelvariabel, i Sales.usp_OrderInfoTT er objektet en midlertidig tabel defineret via SELECT … INTO med en nonclustered tilføjet efterfølgende, og i Sales.usp_OrderInfoTTALT er objektet en foruddefineret midlertidig tabel, som derefter ændres at have en ekstra kolonne. Når dataene er føjet til det midlertidige objekt, er der en SELECT-sætning mod objektet, der slutter sig til tabellen Sales.Kunder.
/* 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
Standardadfærden for SQL Server 2019 er, at tempdb-metadataene ikke er hukommelsesoptimerede, og vi kan bekræfte dette ved at tjekke sys.configurations:
SELECT * FROM sys.configurations WHERE configuration_id = 1589;
For alle tre lagrede procedurer vil vi bruge sqlcmd til at generere 20 samtidige tråde, der kører en af to forskellige .sql-filer. Den første .sql-fil, som vil blive brugt af 19 tråde, vil udføre proceduren i en løkke 1000 gange. Den anden .sql-fil, som kun vil have én (1) tråd, vil udføre proceduren i en løkke 3000 gange. Filen inkluderer også TSQL for at fange to målinger af interesse:samlet varighed og ventestatistikker. Vi vil bruge Query Store til at registrere den gennemsnitlige varighed for 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
Eksempel på kommandolinjefil:
Resultater
Efter at have udført kommandolinjefilerne, der genererer 20 tråde for hver lagret procedure, viser kontrol af den samlede varighed for de 12.000 udførelser af hver procedure følgende:
SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration] FROM [dbo].[PerfTesting_Tests] ORDER BY [TestID];
De lagrede procedurer med de midlertidige tabeller (usp_OrderInfoTT og usp_OrderInfoTTC) tog længere tid at fuldføre. Hvis vi ser på individuelle forespørgselsydelser:
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, at SELECT ... INTO for usp_OrderInfoTT tog omkring 28 ms i gennemsnit (varigheden i Query Store er gemt i mikrosekunder), og kun tog 9 ms, da den midlertidige tabel blev oprettet på forhånd. For tabelvariablen tog INSERT lidt over 22ms i gennemsnit. Interessant nok tog SELECT-forespørgslen lidt over 1 ms for de midlertidige tabeller og ca. 2,7 ms for tabelvariablen.
En kontrol af ventestatistikdata finder en velkendt wait_type, PAGELATCH*:
SELECT * FROM [dbo].[PerfTesting_WaitStats] ORDER BY [TestID], [Percentage] DESC;
Bemærk, at vi kun ser PAGELATCH* venter på test 1 og 2, som var procedurerne med de midlertidige tabeller. For usp_OrderInfoTV, som brugte en tabelvariabel, ser vi kun SOS_SCHEDULER_YIELD-venter. Bemærk venligst: Dette betyder på ingen måde, at du skal bruge tabelvariabler i stedet for midlertidige tabeller , og det betyder heller ikke, at du ikke vil har PAGELATCH-venter med tabelvariabler. Dette er et konstrueret scenarie; Jeg højt anbefaler, at du tester med DIN kode for at se, hvilke wait_types der vises.
Nu vil vi ændre forekomsten til at bruge hukommelsesoptimerede tabeller til tempdb-metadataene. Der er to måder dette kan gøres på, via kommandoen ALTER SERVER CONFIGURATION eller ved at bruge sp_configure. Da denne indstilling er en avanceret mulighed, skal du først aktivere avancerede indstillinger, hvis du bruger sp_configure.
ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; GO
Efter denne ændring er det nødvendigt at genstarte forekomsten. (BEMÆRK:du kan ændre dette tilbage til IKKE at bruge hukommelsesoptimerede tabeller, du skal bare genstarte instansen igen.) Efter genstarten, hvis vi tjekker sys.configurations igen, kan vi se, at metadatatabellerne er hukommelsesoptimerede:
Efter at have udført kommandolinjefilerne igen, viser den samlede varighed for de 21.000 afviklinger af hver procedure følgende (bemærk, at resultaterne er sorteret efter lagret procedure for nemmere sammenligning):
Der var helt sikkert en forbedring i ydeevnen for både usp_OrderInfoTT og usp_OrderInfoTTC , og en lille stigning i ydeevnen for usp_OrderInfoTV. Lad os tjekke forespørgslens varighed:
For alle forespørgsler er forespørgselsvarigheden næsten den samme, bortset fra stigningen i INSERT-varigheden, når tabellen er præ-oprettet, hvilket er fuldstændig uventet. Vi ser en interessant ændring i ventestatistikken:
For usp_OrderInfoTT udføres en SELECT … INTO for at oprette den midlertidige tabel. Ventetiden ændres fra at være PAGELATCH_EX og PAGELATCH_SH til kun PAGELATCH_EX og SOS_SCHEDULER_YIELD. Vi ser ikke længere PAGELATCH_SH-venterne.
For usp_OrderInfoTTC, som opretter den midlertidige tabel og derefter indsætter, vises PAGELATCH_EX og PAGELATCH_SH ventetider ikke længere, og vi ser kun SOS_SCHEDULER_YIELD venter.
Endelig, for OrderInfoTV, er ventetiden konsekvente – kun SOS_SCHEDULER_YIELD, med næsten den samme samlede ventetid.
Oversigt
Baseret på denne test ser vi en forbedring i alle tilfælde, markant for de lagrede procedurer med midlertidige tabeller. Der er en lille ændring for tabelvariablen. Det er ekstremt vigtigt at huske, at dette er et scenarie med en lille belastningstest. Jeg var meget interesseret i at prøve disse tre meget simple scenarier, for at prøve at forstå, hvad der kunne have mest gavn af at gøre tempdb-metadatahukommelsen optimeret. Denne arbejdsbyrde var lille og kørte i meget begrænset tid – faktisk havde jeg mere varierede resultater med flere tråde, hvilket er værd at undersøge i et andet indlæg. Den største fordel er, at som med alle nye funktioner og funktionalitet, er test vigtig. For denne funktion vil du have en basislinje for den aktuelle ydeevne, som du kan sammenligne metrics som f.eks. Batch-anmodninger/Sec og ventestatistikker efter at have gjort metadatahukommelsen optimeret.
Yderligere overvejelser
Brug af In-Memory OLTP kræver en filgruppe af typen MEMORY OPTIMIZED DATA. Efter aktivering af MEMORY_OPTIMIZED TEMPDB_METADATA oprettes der dog ingen yderligere filgruppe for tempdb. Derudover vides det ikke, om de hukommelsesoptimerede tabeller er holdbare (SCHEMA_AND_DATA) eller ej (SCHEMA_ONLY). Typisk kan dette bestemmes via sys.tables (durability_desc), men intet returnerer for de involverede systemtabeller, når du forespørger dette i tempdb, selv når du bruger den dedikerede administratorforbindelse. Du har mulighed for at se ikke-klyngede indekser for de hukommelsesoptimerede tabeller. Du kan bruge følgende forespørgsel til at se, hvilke tabeller der er hukommelsesoptimerede 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 derefter sp_helpindex for enhver af tabellerne, for eksempel:
EXEC sys.sp_helpindex N'sys.sysobjvalues';
Bemærk, at hvis det er et hash-indeks (som kræver estimering af BUCKET_COUNT som en del af oprettelsen), vil beskrivelsen indeholde "ikke-klyngede hash."