sql >> Databasteknik >  >> RDS >> Sqlserver

tempdb-förbättringar i SQL Server 2019

Jag har gett samma rekommendationer om tempdb sedan jag började arbeta med SQL Server för över 15 år sedan, när jag arbetade med kunder som kör version 2000. Kontentan av det:skapa flera datafiler som har samma storlek, med samma auto -tillväxtinställningar, aktivera spårningsflagga 1118 (och kanske 1117) och minska din tempdb-användning. Från kundsidan har detta varit gränsen för vad som kan göras*, fram till SQL Server 2019.

*Det finns några ytterligare kodningsrekommendationer som Pam Lahoud diskuterar i sitt mycket informativa inlägg, TEMPDB – Files and Trace Flags and Updates, Oh My!

Det jag tycker är intressant är att efter all den här tiden är tempdb fortfarande ett problem. SQL Server-teamet har gjort många förändringar under åren för att försöka mildra problem, men missbruket fortsätter. Den senaste anpassningen av SQL Server-teamet flyttar systemtabellerna (metadata) för tempdb till In-Memory OLTP (aka minnesoptimerad). Viss information finns tillgänglig i SQL Server 2019 release notes, och det fanns en demo från Bob Ward och Conor Cunningham under den första dagen av PASS Summit keynote. Pam Lahoud gjorde också en snabb demo i sin allmänna session i PASS Summit. Nu när 2019 CTP 3.2 är ute tänkte jag att det kanske var dags att testa mig själv lite.

Inställningar

Jag har SQL Server 2019 CTP 3.2 installerat på min virtuella maskin, som har 8 GB minne (max serverminne inställt på 6 GB) och 4 vCPU:er. Jag skapade fyra (4) tempdb-datafiler, var och en i storleken 1 GB.

Jag återställde en kopia av WideWorldImporters och skapade sedan tre lagrade procedurer (definitioner nedan). Varje lagrad procedur accepterar en datuminmatning och skickar alla rader från Sales.Order och Sales.OrderLines för det datumet till det tillfälliga objektet. I Sales.usp_OrderInfoTV är objektet en tabellvariabel, i Sales.usp_OrderInfoTT är objektet en temporär tabell definierad via SELECT ... INTO med en icke-klusterad tillagd efteråt, och i Sales.usp_OrderInfoTTALT är objektet en fördefinierad temporär tabell som sedan ändras att ha en extra kolumn. Efter att data har lagts till i det temporära objektet finns det en SELECT-sats mot objektet som ansluter till tabellen 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

Standardbeteendet för SQL Server 2019 är att tempdb-metadata inte är minnesoptimerad, och vi kan bekräfta detta genom att kontrollera sys.configurations:

  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;

För alla tre lagrade procedurer kommer vi att använda sqlcmd för att generera 20 samtidiga trådar som kör en av två olika .sql-filer. Den första .sql-filen, som kommer att användas av 19 trådar, kommer att utföra proceduren i en loop 1000 gånger. Den andra .sql-filen, som bara kommer att ha en (1) tråd, kommer att utföra proceduren i en loop 3000 gånger. Filen inkluderar också TSQL för att fånga två mätvärden av intresse:total varaktighet och väntestatistik. Vi kommer att använda Query Store för att fånga den genomsnittliga varaktigheten för proceduren.

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

Exempel på kommandoradsfil:

Resultat

Efter att ha kört kommandoradsfilerna som genererar 20 trådar för varje lagrad procedur, kontrollerar den totala varaktigheten för de 12 000 körningarna av varje procedur följande:

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

De lagrade procedurerna med de temporära tabellerna (usp_OrderInfoTT och usp_OrderInfoTTC) tog längre tid att slutföra. Om vi ​​tittar på individuella frågeresultat:

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

Vi kan se att SELECT ... INTO för usp_OrderInfoTT tog cirka 28 ms i genomsnitt (varaktigheten i Query Store lagras i mikrosekunder), och bara tog 9 ms när den temporära tabellen var förskapad. För tabellvariabeln tog INSERT drygt 22ms i genomsnitt. Intressant nog tog SELECT-frågan drygt 1 ms för de temporära tabellerna och ungefär 2,7 ms för tabellvariabeln.

En kontroll av väntestatistikdata hittar en bekant wait_type, PAGELATCH*:

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

Lägg märke till att vi bara ser PAGELATCH* väntar på test 1 och 2, som var procedurerna med de temporära tabellerna. För usp_OrderInfoTV, som använde en tabellvariabel, ser vi bara SOS_SCHEDULER_YIELD-väntningar. Observera: Detta innebär inte på något sätt att du ska använda tabellvariabler istället för temporära tabeller , och det betyder inte heller att du inte kommer att göra det har PAGELATCH väntar med tabellvariabler. Detta är ett konstruerat scenario; Jag mycket rekommenderar att du testar med DIN kod för att se vilka wait_types som visas.

Nu kommer vi att ändra instansen för att använda minnesoptimerade tabeller för tempdb-metadata. Det finns två sätt detta kan göras, via kommandot ALTER SERVER CONFIGURATION, eller genom att använda sp_configure. Eftersom den här inställningen är ett avancerat alternativ, om du använder sp_configure måste du först aktivera avancerade alternativ.

ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Efter denna ändring är det nödvändigt att starta om till instansen. (OBS:du kan ändra tillbaka detta till att INTE använda minnesoptimerade tabeller, du måste bara starta om instansen igen.) Efter omstarten, om vi kontrollerar sys.configurations igen kan vi se att metadatatabellerna är minnesoptimerade:

Efter att ha kört kommandoradsfilerna igen visar den totala varaktigheten för de 21 000 körningarna av varje procedur följande (observera att resultaten är ordnade efter lagrad procedur för enklare jämförelse):

Det var definitivt en förbättring av prestanda för både usp_OrderInfoTT och usp_OrderInfoTTC , och en liten ökning av prestanda för usp_OrderInfoTV. Låt oss kontrollera sökfrågans varaktighet:

För alla frågor är frågans varaktighet nästan densamma, förutom ökningen av INSERT-varaktigheten när tabellen är förskapad, vilket är helt oväntat. Vi ser en intressant förändring i väntestatistiken:

För usp_OrderInfoTT exekveras en SELECT … INTO för att skapa den temporära tabellen. Väntetiden ändras från att vara PAGELATCH_EX och PAGELATCH_SH till endast PAGELATCH_EX och SOS_SCHEDULER_YIELD. Vi ser inte längre PAGELATCH_SH-väntningarna.

För usp_OrderInfoTTC, som skapar den temporära tabellen och sedan infogar, visas inte längre PAGELATCH_EX- och PAGELATCH_SH-väntningarna, och vi ser bara SOS_SCHEDULER_YIELD-väntningar.

Slutligen, för OrderInfoTV är väntetiderna konsekventa – bara SOS_SCHEDULER_YIELD, med nästan samma totala väntetid.

Sammanfattning

Baserat på denna testning ser vi en förbättring i alla fall, avsevärt för de lagrade procedurerna med temporära tabeller. Det finns en liten förändring för tabellvariabelproceduren. Det är extremt viktigt att komma ihåg att detta är ett scenario, med ett litet belastningstest. Jag var väldigt intresserad av att prova dessa tre mycket enkla scenarier, för att försöka förstå vad som skulle kunna dra mest nytta av att göra tempdb-metadataminnet optimerat. Denna arbetsbelastning var liten och pågick under en mycket begränsad tid – jag hade faktiskt mer varierande resultat med fler trådar, vilket är värt att utforska i ett annat inlägg. Den största fördelen är att, som med alla nya funktioner och funktionalitet, är testning viktigt. För den här funktionen vill du ha en baslinje för nuvarande prestanda mot vilken du kan jämföra mätvärden som Batch Requests/Sec och väntestatistik efter att ha gjort metadataminnet optimerat.

Ytterligare överväganden

Att använda OLTP i minnet kräver en filgrupp av typen MEMORY OPTIMIZED DATA. Men efter att ha aktiverat MEMORY_OPTIMIZED TEMPDB_METADATA skapas ingen ytterligare filgrupp för tempdb. Dessutom är det inte känt om de minnesoptimerade tabellerna är hållbara (SCHEMA_AND_DATA) eller inte (SCHEMA_ONLY). Vanligtvis kan detta bestämmas via sys.tables (durability_desc), men ingenting returneras för de inblandade systemtabellerna när du frågar efter detta i tempdb, även när du använder den dedikerade administratörsanslutningen. Du har möjlighet att visa icke-klustrade index för de minnesoptimerade tabellerna. Du kan använda följande fråga för att se vilka tabeller som är minnesoptimerade i 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;

Kör sedan sp_helpindex för någon av tabellerna, till exempel:

EXEC sys.sp_helpindex N'sys.sysobjvalues';

Observera att om det är ett hashindex (vilket kräver att man uppskattar BUCKET_COUNT som en del av skapandet), skulle beskrivningen inkludera "icke-klustrad hash."


  1. Hur man beräknar skillnaden mellan två datum i PostgreSQL/Oracle

  2. Ansluter Oracle till SQL Server från Windows

  3. Lagrad procedur och behörigheter - räcker det med EXECUTE?

  4. Ställ in teckenuppsättningen och sorteringen av en databas i MariaDB