sql >> Base de Datos >  >> RDS >> Sqlserver

Mejoras de tempdb en SQL Server 2019

He estado haciendo las mismas recomendaciones sobre tempdb desde que comencé a trabajar con SQL Server hace más de 15 años, cuando trabajaba con clientes que ejecutaban la versión 2000. La esencia de esto:crear múltiples archivos de datos que tengan el mismo tamaño, con el mismo auto -Configuración de crecimiento, habilite el indicador de rastreo 1118 (y tal vez 1117) y reduzca su uso de tempdb. Desde el lado del cliente, este ha sido el límite de lo que se puede hacer*, hasta SQL Server 2019.

*Hay algunas recomendaciones de codificación adicionales que Pam Lahoud analiza en su publicación muy informativa, TEMPDB:archivos y marcas de seguimiento y actualizaciones, ¡Dios mío!

Lo que me parece interesante es que, después de todo este tiempo, tempdb sigue siendo un problema. El equipo de SQL Server ha realizado muchos cambios a lo largo de los años para tratar de mitigar los problemas, pero el abuso continúa. La última adaptación del equipo de SQL Server está moviendo las tablas del sistema (metadatos) para tempdb a In-Memory OLTP (también conocido como optimizado para memoria). Parte de la información está disponible en las notas de la versión de SQL Server 2019, y hubo una demostración de Bob Ward y Conor Cunningham durante el primer día del discurso de apertura de PASS Summit. Pam Lahoud también hizo una demostración rápida en su sesión general de PASS Summit. Ahora que salió el CTP 3.2 de 2019, pensé que podría ser el momento de hacer algunas pruebas.

Configuración

Tengo SQL Server 2019 CTP 3.2 instalado en mi máquina virtual, que tiene 8 GB de memoria (memoria máxima del servidor establecida en 6 GB) y 4 vCPU. Creé cuatro (4) archivos de datos tempdb, cada uno con un tamaño de 1 GB.

Restauré una copia de WideWorldImporters y luego creé tres procedimientos almacenados (definiciones a continuación). Cada procedimiento almacenado acepta una entrada de fecha y envía todas las filas de Sales.Order y Sales.OrderLines para esa fecha al objeto temporal. En Sales.usp_OrderInfoTV, el objeto es una variable de tabla, en Sales.usp_OrderInfoTT, el objeto es una tabla temporal definida a través de SELECT … INTO con un agregado no agrupado después, y en Sales.usp_OrderInfoTTALT, el objeto es una tabla temporal predefinida que luego se modifica tener una columna adicional. Después de agregar los datos al objeto temporal, hay una declaración SELECT contra el objeto que se une a la tabla 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

Pruebas

El comportamiento predeterminado para SQL Server 2019 es que los metadatos de tempdb no están optimizados para memoria, y podemos confirmarlo comprobando sys.configurations:

  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;

Para los tres procedimientos almacenados, usaremos sqlcmd para generar 20 subprocesos simultáneos que ejecutan uno de dos archivos .sql diferentes. El primer archivo .sql, que será utilizado por 19 subprocesos, ejecutará el procedimiento en un bucle 1000 veces. El segundo archivo .sql, que solo tendrá un (1) hilo, ejecutará el procedimiento en un bucle 3000 veces. El archivo también incluye TSQL para capturar dos métricas de interés:duración total y estadísticas de espera. Usaremos Query Store para capturar la duración promedio del procedimiento.

  /*
  	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

Ejemplo de archivo de línea de comando:

Resultados

Después de ejecutar los archivos de la línea de comandos que generan 20 subprocesos para cada procedimiento almacenado, al comprobar la duración total de las 12 000 ejecuciones de cada procedimiento se muestra lo siguiente:

  SELECT *, DATEDIFF(SECOND, TestStartTime, TestEndTime) AS [TotalDuration]
  FROM [dbo].[PerfTesting_Tests]
  ORDER BY [TestID];

Los procedimientos almacenados con las tablas temporales (usp_OrderInfoTT y usp_OrderInfoTTC) tardaron más en completarse. Si observamos el rendimiento de consultas individuales:

  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 SELECT … INTO para usp_OrderInfoTT tomó alrededor de 28 ms en promedio (la duración en Query Store se almacena en microsegundos), y solo tomó 9 ms cuando la tabla temporal se creó previamente. Para la variable de la tabla, INSERT tardó un poco más de 22 ms en promedio. Curiosamente, la consulta SELECT tardó poco más de 1 ms para las tablas temporales y aproximadamente 2,7 ms para la variable de tabla.

Una verificación de los datos de estadísticas de espera encuentra un tipo de espera familiar, PAGELATCH*:

  SELECT * 
  FROM [dbo].[PerfTesting_WaitStats]
  ORDER BY [TestID], [Percentage] DESC;

Observe que solo vemos que PAGELATCH* espera las pruebas 1 y 2, que eran los procedimientos con las tablas temporales. Para usp_OrderInfoTV, que usaba una variable de tabla, solo vemos esperas SOS_SCHEDULER_YIELD. Tenga en cuenta: Esto no implica de ninguna manera que deba usar variables de tabla en lugar de tablas temporales , ni implica que no tener PAGELATCH espera con variables de tabla. Este es un escenario artificial; Yo muy le recomendamos que pruebe con SU código para ver qué tipos de espera aparecen.

Ahora cambiaremos la instancia para usar tablas optimizadas para memoria para los metadatos de tempdb. Hay dos formas de hacerlo, mediante el comando ALTER SERVER CONFIGURATION o mediante sp_configure. Como esta configuración es una opción avanzada, si usa sp_configure, primero deberá habilitar las opciones avanzadas.

ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Después de este cambio, es necesario reiniciar la instancia. (NOTA:puede volver a cambiar esto para NO usar tablas optimizadas para memoria, solo tiene que reiniciar la instancia nuevamente). Después del reinicio, si verificamos sys.configurations nuevamente, podemos ver que las tablas de metadatos están optimizadas para memoria:

Después de volver a ejecutar los archivos de la línea de comandos, la duración total de las 21 000 ejecuciones de cada procedimiento muestra lo siguiente (tenga en cuenta que los resultados están ordenados por procedimiento almacenado para facilitar la comparación):

Definitivamente hubo una mejora en el rendimiento tanto para usp_OrderInfoTT como para usp_OrderInfoTTC, y un ligero aumento en el rendimiento para usp_OrderInfoTV. Verifiquemos las duraciones de las consultas:

Para todas las consultas, la duración de la consulta es casi la misma, excepto por el aumento en la duración de INSERT cuando la tabla se crea previamente, lo cual es completamente inesperado. Vemos un cambio interesante en las estadísticas de espera:

Para usp_OrderInfoTT, se ejecuta SELECT … INTO para crear la tabla temporal. Las esperas pasan de ser PAGELATCH_EX y PAGELATCH_SH a solo PAGELATCH_EX y SOS_PROGRAMADOR_RENDIMIENTO. Ya no vemos las esperas de PAGELATCH_SH.

Para usp_OrderInfoTTC, que crea la tabla temporal y luego inserta, las esperas PAGELATCH_EX y PAGELATCH_SH ya no aparecen, y solo vemos las esperas SOS_SCHEDULER_YIELD.

Finalmente, para OrderInfoTV, las esperas son consistentes:solo SOS_SCHEDULER_YIELD, con casi el mismo tiempo de espera total.

Resumen

Según esta prueba, vemos una mejora en todos los casos, significativamente para los procedimientos almacenados con tablas temporales. Hay un ligero cambio para el procedimiento de variable de tabla. Es extremadamente importante recordar que este es un escenario, con una pequeña prueba de carga. Estaba muy interesado en probar estos tres escenarios muy simples, para tratar de comprender qué podría beneficiarse más al optimizar la memoria de los metadatos de tempdb. Esta carga de trabajo fue pequeña y duró un tiempo muy limitado; de hecho, obtuve resultados más variados con más subprocesos, lo que vale la pena explorar en otra publicación. La conclusión más importante es que, al igual que con todas las características y funcionalidades nuevas, las pruebas son importantes. Para esta característica, desea tener una referencia del rendimiento actual contra la cual comparar métricas como solicitudes por lotes/seg y estadísticas de espera después de optimizar la memoria de los metadatos.

Consideraciones adicionales

El uso de OLTP en memoria requiere un grupo de archivos del tipo DATOS OPTIMIZADOS DE MEMORIA. Sin embargo, después de habilitar MEMORY_OPTIMIZED TEMPDB_METADATA, no se crea ningún grupo de archivos adicional para tempdb. Además, no se sabe si las tablas optimizadas para memoria son duraderas (SCHEMA_AND_DATA) o no (SCHEMA_ONLY). Por lo general, esto se puede determinar a través de sys.tables (durability_desc), pero no se devuelve nada para las tablas del sistema involucradas cuando se consulta esto en tempdb, incluso cuando se usa la conexión de administrador dedicado. Tiene la capacidad de ver índices no agrupados para las tablas optimizadas para memoria. Puede usar la siguiente consulta para ver qué tablas están optimizadas para memoria en 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;

Luego, para cualquiera de las tablas, ejecute sp_helpindex, por ejemplo:

EXEC sys.sp_helpindex N'sys.sysobjvalues';

Tenga en cuenta que si se trata de un índice hash (que requiere estimar BUCKET_COUNT como parte de la creación), la descripción incluiría "hash no agrupado".