Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Tempdb Miglioramenti in SQL Server 2019

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] &gt; 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".