Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Aprimoramentos do tempdb no SQL Server 2019


Tenho feito as mesmas recomendações sobre tempdb desde que comecei a trabalhar com SQL Server há mais de 15 anos, quando trabalhava com clientes executando a versão 2000. A essência disso:crie vários arquivos de dados com o mesmo tamanho, com o mesmo auto -crescimento, habilite o sinalizador de rastreamento 1118 (e talvez 1117) e reduza o uso do tempdb. Do lado do cliente, esse foi o limite do que pode ser feito*, até o SQL Server 2019.

*Existem algumas recomendações de codificação adicionais que Pam Lahoud discute em seu post muito informativo, TEMPDB – Arquivos e sinalizadores de rastreamento e atualizações, meu Deus!

O que acho interessante é que, depois de todo esse tempo, o tempdb ainda é um problema. A equipe do SQL Server fez muitas alterações ao longo dos anos para tentar mitigar os problemas, mas o abuso continua. A adaptação mais recente da equipe do SQL Server está movendo as tabelas do sistema (metadados) para tempdb para OLTP na memória (também conhecido como otimizado para memória). Algumas informações estão disponíveis nas notas de versão do SQL Server 2019 e houve uma demonstração de Bob Ward e Conor Cunningham durante o primeiro dia da palestra do PASS Summit. Pam Lahoud também fez uma demonstração rápida em sua sessão geral do PASS Summit. Agora que o CTP 3.2 de 2019 foi lançado, pensei que talvez fosse hora de fazer um pouco de teste.

Configuração


Eu tenho o SQL Server 2019 CTP 3.2 instalado na minha máquina virtual, que tem 8 GB de memória (memória máxima do servidor definida como 6 GB) e 4 vCPUs. Criei quatro (4) arquivos de dados tempdb, cada um com tamanho de 1 GB.

Eu restaurei uma cópia do WideWorldImporters e depois criei três procedimentos armazenados (definições abaixo). Cada procedimento armazenado aceita uma entrada de data e envia todas as linhas de Sales.Order e Sales.OrderLines dessa data para o objeto temporário. Em Sales.usp_OrderInfoTV o objeto é uma variável de tabela, em Sales.usp_OrderInfoTT o objeto é uma tabela temporária definida via SELECT … INTO com um nonclustered adicionado posteriormente, e em Sales.usp_OrderInfoTTALT o objeto é uma tabela temporária pré-definida que depois é alterada para ter uma coluna adicional. Depois que os dados são adicionados ao objeto temporário, há uma instrução SELECT em relação ao objeto que se une à tabela 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

Teste


O comportamento padrão do SQL Server 2019 é que os metadados tempdb não são otimizados para memória e podemos confirmar isso verificando sys.configurations:
  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;



Para todos os três procedimentos armazenados, usaremos o sqlcmd para gerar 20 threads simultâneos executando um dos dois arquivos .sql diferentes. O primeiro arquivo .sql, que será usado por 19 threads, executará o procedimento em um loop 1000 vezes. O segundo arquivo .sql, que terá apenas uma (1) thread, executará o procedimento em um loop 3000 vezes. O arquivo também inclui TSQL para capturar duas métricas de interesse:duração total e estatísticas de espera. Usaremos o Query Store para capturar a duração média do procedimento.
  /*
  	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

Exemplo de arquivo de linha de comando:


Resultados


Após executar os arquivos de linha de comando que geram 20 threads para cada procedimento armazenado, a verificação da duração total das 12.000 execuções de cada procedimento mostra o seguinte:
  SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration]
  FROM [dbo].[PerfTesting_Tests]
  ORDER BY [TestID];



Os procedimentos armazenados com as tabelas temporárias (usp_OrderInfoTT e usp_OrderInfoTTC) demoraram mais para serem concluídos. Se observarmos o desempenho da consulta individual:
  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];



Podemos ver que o SELECT … INTO para usp_OrderInfoTT levou cerca de 28ms em média (a duração no Query Store é armazenada em microssegundos), e levou apenas 9ms quando a tabela temporária foi pré-criada. Para a variável tabela, o INSERT levou em média pouco mais de 22ms. Curiosamente, a consulta SELECT levou pouco mais de 1ms para as tabelas temporárias e aproximadamente 2,7ms para a variável de tabela.

Uma verificação dos dados de estatísticas de espera encontra um wait_type familiar, PAGELATCH*:
  SELECT * 
  FROM [dbo].[PerfTesting_WaitStats]
  ORDER BY [TestID], [Percentage] DESC;



Observe que só vemos as esperas de PAGELATCH* pelos testes 1 e 2, que eram os procedimentos com as tabelas temporárias. Para usp_OrderInfoTV, que usava uma variável de tabela, vemos apenas esperas SOS_SCHEDULER_YIELD. Observação: Isso não implica de forma alguma que você deva usar variáveis ​​de tabela em vez de tabelas temporárias , nem implica que você não tem esperas PAGELATCH com variáveis ​​de tabela. Este é um cenário artificial; Eu altamente recomendo que você teste com SEU código para ver quais wait_types aparecem.

Agora vamos alterar a instância para usar tabelas com otimização de memória para os metadados tempdb. Há duas maneiras de fazer isso, por meio do comando ALTER SERVER CONFIGURATION ou usando sp_configure. Como essa configuração é uma opção avançada, se você usar sp_configure, precisará habilitar as opções avançadas primeiro.
ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Após essa alteração é necessário reiniciar a instância. (OBSERVAÇÃO:você pode alterar isso de volta para NÃO usar tabelas com otimização de memória, basta reiniciar a instância novamente.) Após a reinicialização, se verificarmos sys.configurations novamente, podemos ver que as tabelas de metadados são otimizadas para memória:



Depois de executar os arquivos de linha de comando novamente, a duração total das 21.000 execuções de cada procedimento mostra o seguinte (observe que os resultados são ordenados por procedimento armazenado para facilitar a comparação):



Definitivamente, houve uma melhoria no desempenho de usp_OrderInfoTT e usp_OrderInfoTTC e um ligeiro aumento no desempenho de usp_OrderInfoTV. Vamos verificar as durações das consultas:



Para todas as consultas, a duração da consulta é quase a mesma, exceto pelo aumento na duração de INSERT quando a tabela é pré-criada, o que é completamente inesperado. Vemos uma mudança interessante nas estatísticas de espera:



Para usp_OrderInfoTT, um SELECT … INTO é executado para criar a tabela temporária. As esperas mudam de PAGELATCH_EX e PAGELATCH_SH para somente PAGELATCH_EX e SOS_SCHEDULER_YIELD. Não vemos mais as esperas PAGELATCH_SH.

Para usp_OrderInfoTTC, que cria a tabela temporária e depois insere, as esperas PAGELATCH_EX e PAGELATCH_SH não aparecem mais, e vemos apenas esperas SOS_SCHEDULER_YIELD.

Finalmente, para OrderInfoTV, as esperas são consistentes – apenas SOS_SCHEDULER_YIELD, com quase o mesmo tempo total de espera.

Resumo


Com base neste teste, vemos uma melhoria em todos os casos, significativamente para os procedimentos armazenados com tabelas temporárias. Há uma pequena mudança para o procedimento de variável de tabela. É extremamente importante lembrar que este é um cenário, com um pequeno teste de carga. Eu estava muito interessado em experimentar esses três cenários muito simples, para tentar entender o que poderia se beneficiar mais ao otimizar a memória dos metadados do tempdb. Essa carga de trabalho era pequena e durou um tempo muito limitado – na verdade tive resultados mais variados com mais threads, que vale a pena explorar em outro post. A maior vantagem é que, como acontece com todos os novos recursos e funcionalidades, o teste é importante. Para esse recurso, você deseja ter uma linha de base do desempenho atual com a qual comparar métricas como solicitações de lote/s e estatísticas de espera depois de otimizar a memória dos metadados.

Considerações adicionais


O uso de OLTP na memória requer um grupo de arquivos do tipo MEMORY OPTIMIZED DATA. No entanto, após habilitar MEMORY_OPTIMIZED TEMPDB_METADATA, nenhum grupo de arquivos adicional é criado para tempdb. Além disso, não se sabe se as tabelas com otimização de memória são duráveis ​​(SCHEMA_AND_DATA) ou não (SCHEMA_ONLY). Normalmente, isso pode ser determinado por meio de sys.tables (durability_desc), mas nada retorna para as tabelas de sistema envolvidas ao consultar isso no tempdb, mesmo ao usar a Conexão de Administrador Dedicado. Você tem a capacidade de exibir índices não clusterizados para as tabelas com otimização de memória. Você pode usar a seguinte consulta para ver quais tabelas são otimizadas para memória no 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;

Em seguida, para qualquer uma das tabelas, execute sp_helpindex, por exemplo:
EXEC sys.sp_helpindex N'sys.sysobjvalues';



Observe que, se for um índice de hash (o que requer a estimativa de BUCKET_COUNT como parte da criação), a descrição incluirá "hash não clusterizado".