Ho fornito gli stessi consigli su tempdb da quando ho iniziato a lavorare con SQL Server oltre 15 anni fa, quando lavoravo con clienti che eseguivano la versione 2000. Il succo di tutto:creare più file di dati con le stesse dimensioni, con la stessa auto - impostazioni di crescita, abilita il flag di traccia 1118 (e forse 1117) e riduci l'uso di tempdb. Dal lato del cliente, questo è stato il limite di ciò che si può fare*, fino a SQL Server 2019.
*Ci sono alcuni consigli aggiuntivi sulla codifica di cui Pam Lahoud discute nel suo post molto informativo, TEMPDB – Files and Trace Flags and Updates, Oh mio!
Quello che trovo interessante è che, dopo tutto questo tempo, tempdb è ancora un problema. Il team di SQL Server ha apportato molte modifiche nel corso degli anni per cercare di mitigare i problemi, ma l'abuso continua. L'ultimo adattamento del team di SQL Server sta spostando le tabelle di sistema (metadati) per tempdb in OLTP in memoria (ovvero con ottimizzazione per la memoria). Alcune informazioni sono disponibili nelle note sulla versione di SQL Server 2019 e c'era una demo di Bob Ward e Conor Cunningham durante il primo giorno del keynote del PASS Summit. Pam Lahoud ha anche fatto una rapida demo nella sua sessione generale del PASS Summit. Ora che 2019 CTP 3.2 è uscito, ho pensato che sarebbe arrivato il momento di fare un po' di test su me stesso.
Configurazione
Ho SQL Server 2019 CTP 3.2 installato sulla mia macchina virtuale, che ha 8 GB di memoria (memoria massima del server impostata su 6 GB) e 4 vCPU. Ho creato quattro (4) file di dati tempdb, ciascuno delle dimensioni di 1 GB.
Ho ripristinato una copia di WideWorldImporters e quindi ho creato tre stored procedure (definizioni di seguito). Ogni stored procedure accetta un input di data e inserisce tutte le righe da Sales.Order e Sales.OrderLines per quella data nell'oggetto temporaneo. In Sales.usp_OrderInfoTV l'oggetto è una variabile di tabella, in Sales.usp_OrderInfoTT l'oggetto è una tabella temporanea definita tramite SELECT … INTO con un non cluster aggiunto in seguito e in Sales.usp_OrderInfoTTALT l'oggetto è una tabella temporanea predefinita che viene quindi modificata avere una colonna aggiuntiva. Dopo che i dati sono stati aggiunti all'oggetto temporaneo, c'è un'istruzione SELECT sull'oggetto che si unisce alla tabella 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
Il comportamento predefinito per SQL Server 2019 è che i metadati tempdb non sono ottimizzati per la memoria e possiamo confermarlo controllando sys.configurations:
SELECT * FROM sys.configurations WHERE configuration_id = 1589;
Per tutte e tre le procedure memorizzate utilizzeremo sqlcmd per generare 20 thread simultanei che eseguono uno di due diversi file .sql. Il primo file .sql, che verrà utilizzato da 19 thread, eseguirà la procedura in un ciclo 1000 volte. Il secondo file .sql, che avrà solo un (1) thread, eseguirà la procedura in un ciclo 3000 volte. Il file include anche TSQL per acquisire due metriche di interesse:durata totale e statistiche di attesa. Utilizzeremo Query Store per acquisire la durata media della procedura.
/* 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
Esempio di file della riga di comando:
Risultati
Dopo aver eseguito i file della riga di comando che generano 20 thread per ciascuna procedura memorizzata, il controllo della durata totale delle 12.000 esecuzioni di ciascuna procedura mostra quanto segue:
SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration] FROM [dbo].[PerfTesting_Tests] ORDER BY [TestID];
Le procedure memorizzate con le tabelle temporanee (usp_OrderInfoTT e usp_OrderInfoTTC) hanno richiesto più tempo per essere completate. Se esaminiamo le prestazioni delle singole query:
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];
Possiamo vedere che SELECT … INTO per usp_OrderInfoTT ha impiegato in media circa 28 ms (la durata in Query Store è memorizzata in microsecondi) e ha impiegato solo 9 ms quando la tabella temporanea è stata pre-creata. Per la variabile table, INSERT ha richiesto in media poco più di 22 ms. È interessante notare che la query SELECT ha richiesto poco più di 1 ms per le tabelle temporanee e circa 2,7 ms per la variabile di tabella.
Un controllo dei dati delle statistiche di attesa trova un wait_type familiare, PAGELATCH*:
SELECT * FROM [dbo].[PerfTesting_WaitStats] ORDER BY [TestID], [Percentage] DESC;
Si noti che vediamo solo PAGELATCH* attende per i test 1 e 2, che erano le procedure con le tabelle temporanee. Per usp_OrderInfoTV, che utilizzava una variabile di tabella, vediamo solo le attese SOS_SCHEDULER_YIELD. Nota: Ciò non implica in alcun modo che dovresti usare variabili di tabella invece di tabelle temporanee , né implica che tu non hanno PAGELATCH attese con variabili di tabella. Questo è uno scenario forzato; Io molto ti consiglio di testare con il TUO codice per vedere quali wait_types appaiono.
Ora cambieremo l'istanza per usare le tabelle ottimizzate per la memoria per i metadati tempdb. Esistono due modi per eseguire questa operazione, tramite il comando ALTER SERVER CONFIGURATION o utilizzando sp_configure. Poiché questa impostazione è un'opzione avanzata, se usi sp_configure dovrai prima abilitare le opzioni avanzate.
ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; GO
Dopo questa modifica è necessario riavviare l'istanza. (NOTA:puoi ripristinarlo in modo da NON utilizzare tabelle ottimizzate per la memoria, devi solo riavviare l'istanza.) Dopo il riavvio, se controlliamo nuovamente sys.configurations, possiamo vedere che le tabelle di metadati sono ottimizzate per la memoria:
Dopo aver eseguito nuovamente i file della riga di comando, la durata totale delle 21.000 esecuzioni di ciascuna procedura mostra quanto segue (si noti che i risultati sono ordinati per procedura memorizzata per un confronto più semplice):
C'è stato sicuramente un miglioramento delle prestazioni sia per usp_OrderInfoTT che per usp_OrderInfoTTC e un leggero aumento delle prestazioni per usp_OrderInfoTV. Controlliamo la durata delle query:
Per tutte le query, la durata della query è quasi la stessa, ad eccezione dell'aumento della durata di INSERT quando la tabella è pre-creata, il che è del tutto imprevisto. Vediamo un interessante cambiamento nelle statistiche di attesa:
Per usp_OrderInfoTT, viene eseguito un SELECT … INTO per creare la tabella temporanea. Le attese cambiano da PAGELATCH_EX e PAGELATCH_SH a solo PAGELATCH_EX e SOS_SCHEDULER_YIELD. Non vediamo più le attese di PAGELATCH_SH.
Per usp_OrderInfoTTC, che crea la tabella temporanea e quindi inserisce, le attese PAGELATCH_EX e PAGELATCH_SH non vengono più visualizzate e vediamo solo le attese SOS_SCHEDULER_YIELD.
Infine, per OrderInfoTV, le attese sono coerenti:solo SOS_SCHEDULER_YIELD, con quasi lo stesso tempo di attesa totale.
Riepilogo
Sulla base di questo test, vediamo un miglioramento in tutti i casi, in modo significativo per le stored procedure con tabelle temporanee. C'è una leggera modifica per la procedura delle variabili di tabella. È estremamente importante ricordare che questo è uno scenario, con un piccolo test di carico. Ero molto interessato a provare questi tre scenari molto semplici, per cercare di capire cosa potrebbe trarre il massimo vantaggio dall'ottimizzazione della memoria dei metadati tempdb. Questo carico di lavoro era piccolo ed è stato eseguito per un tempo molto limitato, infatti ho avuto risultati più vari con più thread, che vale la pena esplorare in un altro post. Il più grande vantaggio è che, come per tutte le nuove funzionalità e funzionalità, il test è importante. Per questa funzione, desideri avere una linea di base delle prestazioni attuali rispetto alla quale confrontare metriche come Richieste batch/Sec e statistiche di attesa dopo aver ottimizzato la memoria dei metadati.
Ulteriori considerazioni
L'utilizzo di In-Memory OLTP richiede un filegroup del tipo MEMORY OPTIMIZED DATA. Tuttavia, dopo aver abilitato MEMORY_OPTIMIZED TEMPDB_METADATA, non viene creato alcun filegroup aggiuntivo per tempdb. Inoltre, non è noto se le tabelle ottimizzate per la memoria siano durevoli (SCHEMA_AND_DATA) o meno (SCHEMA_ONLY). In genere questo può essere determinato tramite sys.tables (durability_desc), ma non viene restituito nulla per le tabelle di sistema coinvolte quando si esegue una query in tempdb, anche quando si utilizza la connessione amministratore dedicata. Hai la possibilità di visualizzare gli indici non cluster per le tabelle con ottimizzazione per la memoria. È possibile utilizzare la query seguente per vedere quali tabelle sono ottimizzate per la memoria in 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;
Quindi, per una qualsiasi delle tabelle, esegui sp_helpindex, ad esempio:
EXEC sys.sp_helpindex N'sys.sysobjvalues';
Tieni presente che se si tratta di un indice hash (che richiede la stima di BUCKET_COUNT come parte della creazione), la descrizione includerebbe "hash non cluster".