Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Améliorations de tempdb dans SQL Server 2019

J'ai fait les mêmes recommandations à propos de tempdb depuis que j'ai commencé à travailler avec SQL Server il y a plus de 15 ans, lorsque je travaillais avec des clients exécutant la version 2000. L'essentiel :créer plusieurs fichiers de données de la même taille, avec la même - paramètres de croissance, activez l'indicateur de trace 1118 (et peut-être 1117) et réduisez votre utilisation de tempdb. Du côté client, cela a été la limite de ce qui peut être fait*, jusqu'à SQL Server 2019.

* Il y a quelques recommandations de codage supplémentaires dont Pam Lahoud discute dans son article très instructif, TEMPDB - Files and Trace Flags and Updates, Oh My !

Ce que je trouve intéressant, c'est qu'après tout ce temps, tempdb est toujours un problème. L'équipe SQL Server a apporté de nombreuses modifications au fil des ans pour essayer d'atténuer les problèmes, mais les abus continuent. La dernière adaptation de l'équipe SQL Server consiste à déplacer les tables système (métadonnées) de tempdb vers OLTP en mémoire (c'est-à-dire à mémoire optimisée). Certaines informations sont disponibles dans les notes de publication de SQL Server 2019, et il y a eu une démonstration de Bob Ward et Conor Cunningham lors de la première journée du discours d'ouverture du PASS Summit. Pam Lahoud a également fait une démonstration rapide lors de sa session générale du PASS Summit. Maintenant que le CTP 3.2 2019 est sorti, j'ai pensé qu'il serait peut-être temps de faire quelques tests moi-même.

Configuration

J'ai SQL Server 2019 CTP 3.2 installé sur ma machine virtuelle, qui dispose de 8 Go de mémoire (mémoire maximale du serveur définie sur 6 Go) et de 4 vCPU. J'ai créé quatre (4) fichiers de données tempdb, chacun dimensionné à 1 Go.

J'ai restauré une copie de WideWorldImporters, puis créé trois procédures stockées (définitions ci-dessous). Chaque procédure stockée accepte une entrée de date et pousse toutes les lignes de Sales.Order et Sales.OrderLines pour cette date dans l'objet temporaire. Dans Sales.usp_OrderInfoTV l'objet est une variable de table, dans Sales.usp_OrderInfoTT l'objet est une table temporaire définie via SELECT … INTO avec un non-cluster ajouté ensuite, et dans Sales.usp_OrderInfoTTALT l'objet est une table temporaire prédéfinie qui est ensuite modifiée avoir une colonne supplémentaire. Une fois les données ajoutées à l'objet temporaire, une instruction SELECT est appliquée à l'objet qui se joint à la table 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

Le comportement par défaut pour SQL Server 2019 est que les métadonnées tempdb ne sont pas optimisées en mémoire, et nous pouvons le confirmer en vérifiant sys.configurations :

  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;

Pour les trois procédures stockées, nous utiliserons sqlcmd pour générer 20 threads simultanés exécutant l'un des deux fichiers .sql différents. Le premier fichier .sql, qui sera utilisé par 19 threads, exécutera la procédure en boucle 1000 fois. Le deuxième fichier .sql, qui n'aura qu'un (1) thread, exécutera la procédure en boucle 3000 fois. Le fichier inclut également TSQL pour capturer deux métriques intéressantes :la durée totale et les statistiques d'attente. Nous utiliserons Query Store pour capturer la durée moyenne de la procédure.

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

Exemple de fichier de ligne de commande :

Résultats

Après avoir exécuté les fichiers de ligne de commande qui génèrent 20 threads pour chaque procédure stockée, la vérification de la durée totale des 12 000 exécutions de chaque procédure indique ce qui suit :

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

Les procédures stockées avec les tables temporaires (usp_OrderInfoTT et usp_OrderInfoTTC) prenaient plus de temps à se terminer. Si nous examinons les performances des requêtes individuelles :

  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];

Nous pouvons voir que le SELECT … INTO pour usp_OrderInfoTT a pris environ 28 ms en moyenne (la durée dans Query Store est stockée en microsecondes), et n'a pris que 9 ms lorsque la table temporaire a été pré-créée. Pour la variable de table, l'INSERT a pris un peu plus de 22 ms en moyenne. Fait intéressant, la requête SELECT a pris un peu plus de 1 ms pour les tables temporaires et environ 2,7 ms pour la variable de table.

Une vérification des données des statistiques d'attente trouve un type d'attente familier, PAGELATCH* :

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

Notez que nous ne voyons que les attentes de PAGELATCH* pour les tests 1 et 2, qui étaient les procédures avec les tables temporaires. Pour usp_OrderInfoTV, qui utilisait une variable de table, nous ne voyons que les attentes SOS_SCHEDULER_YIELD. Veuillez noter : Cela n'implique en aucun cas que vous deviez utiliser des variables de table au lieu de tables temporaires , cela ne signifie pas non plus que vous ne ferez pas avoir PAGELATCH attend avec des variables de table. C'est un scénario artificiel; Je fortement vous recommandons de tester avec VOTRE code pour voir quels types d'attente apparaissent.

Nous allons maintenant modifier l'instance pour utiliser des tables à mémoire optimisée pour les métadonnées tempdb. Cela peut être fait de deux manières, via la commande ALTER SERVER CONFIGURATION ou en utilisant sp_configure. Comme ce paramètre est une option avancée, si vous utilisez sp_configure, vous devez d'abord activer les options avancées.

ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Après ce changement, il est nécessaire de redémarrer l'instance. (REMARQUE :vous pouvez revenir à NE PAS utiliser les tables optimisées en mémoire, il vous suffit de redémarrer l'instance.) Après le redémarrage, si nous vérifions à nouveau sys.configurations, nous pouvons voir que les tables de métadonnées sont optimisées en mémoire :

Après avoir exécuté à nouveau les fichiers de ligne de commande, la durée totale des 21 000 exécutions de chaque procédure indique ce qui suit (notez que les résultats sont classés par procédure stockée pour faciliter la comparaison) :

Il y a certainement eu une amélioration des performances pour usp_OrderInfoTT et usp_OrderInfoTTC , et une légère augmentation des performances pour usp_OrderInfoTV. Vérifions les durées des requêtes :

Pour toutes les requêtes, la durée de la requête est presque la même, à l'exception de l'augmentation de la durée INSERT lorsque la table est pré-créée, ce qui est complètement inattendu. Nous constatons un changement intéressant dans les statistiques d'attente :

Pour usp_OrderInfoTT, un SELECT … INTO est exécuté pour créer la table temporaire. Les temps d'attente passent de PAGELATCH_EX et PAGELATCH_SH à uniquement PAGELATCH_EX et SOS_SCHEDULER_YIELD. On ne voit plus les attentes PAGELATCH_SH.

Pour usp_OrderInfoTTC, qui crée la table temporaire puis insère, les attentes PAGELATCH_EX et PAGELATCH_SH n'apparaissent plus, et nous ne voyons que les attentes SOS_SCHEDULER_YIELD.

Enfin, pour OrderInfoTV, les temps d'attente sont cohérents - seulement SOS_SCHEDULER_YIELD, avec presque le même temps d'attente total.

Résumé

Sur la base de ces tests, nous constatons une amélioration dans tous les cas, de manière significative pour les procédures stockées avec des tables temporaires. Il y a un léger changement pour la procédure de variable de table. Il est extrêmement important de se rappeler qu'il s'agit d'un scénario, avec un petit test de charge. J'étais très intéressé à essayer ces trois scénarios très simples, pour essayer de comprendre ce qui pourrait bénéficier le plus de l'optimisation de la mémoire des métadonnées tempdb. Cette charge de travail était petite et a duré très peu de temps - en fait, j'ai eu des résultats plus variés avec plus de threads, ce qui mérite d'être exploré dans un autre article. Le plus grand point à retenir est que, comme pour toutes les nouvelles fonctionnalités et fonctionnalités, les tests sont importants. Pour cette fonctionnalité, vous souhaitez disposer d'une base de référence des performances actuelles à laquelle comparer des métriques telles que les requêtes par lot/seconde et les statistiques d'attente après avoir optimisé la mémoire des métadonnées.

Considérations supplémentaires

L'utilisation d'OLTP en mémoire nécessite un groupe de fichiers de type MEMORY OPTIMIZED DATA. Cependant, après avoir activé MEMORY_OPTIMIZED TEMPDB_METADATA, aucun groupe de fichiers supplémentaire n'est créé pour tempdb. De plus, on ne sait pas si les tables optimisées en mémoire sont durables (SCHEMA_AND_DATA) ou non (SCHEMA_ONLY). Généralement, cela peut être déterminé via sys.tables (durability_desc), mais rien ne revient pour les tables système impliquées lors de la requête dans tempdb, même lors de l'utilisation de la connexion administrateur dédiée. Vous avez la possibilité d'afficher les index non clusterisés pour les tables à mémoire optimisée. Vous pouvez utiliser la requête suivante pour voir quelles tables sont optimisées en mémoire dans 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;

Ensuite, pour l'une des tables, exécutez sp_helpindex, par exemple :

EXEC sys.sp_helpindex N'sys.sysobjvalues';

Notez que s'il s'agit d'un index de hachage (ce qui nécessite l'estimation de BUCKET_COUNT dans le cadre de la création), la description inclurait "hachage non clusterisé".