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

tempdb-forbedringer i SQL Server 2019

Jeg har givet de samme anbefalinger om tempdb, siden jeg begyndte at arbejde med SQL Server for over 15 år siden, da jeg arbejdede med kunder, der kørte version 2000. Essensen af ​​det:opret flere datafiler, der har samme størrelse, med den samme auto -Vækstindstillinger, aktiver sporingsflag 1118 (og måske 1117), og reducer dit tempdb-brug. Fra kundesiden har dette været grænsen for, hvad der kan lade sig gøre*, indtil SQL Server 2019.

*Der er et par yderligere kodningsanbefalinger, som Pam Lahoud diskuterer i sit meget informative indlæg, TEMPDB – Files and Trace Flags and Updates, Oh My!

Det, jeg finder interessant, er, at efter al denne tid er tempdb stadig et problem. SQL Server-teamet har foretaget mange ændringer gennem årene for at forsøge at afbøde problemer, men misbruget fortsætter. Den seneste tilpasning fra SQL Server-teamet flytter systemtabellerne (metadata) for tempdb til In-Memory OLTP (alias hukommelsesoptimeret). Nogle oplysninger er tilgængelige i SQL Server 2019 release notes, og der var en demo fra Bob Ward og Conor Cunningham under den første dag af PASS Summit keynote. Pam Lahoud lavede også en hurtig demo i hendes PASS Summit general session. Nu hvor 2019 CTP 3.2 er ude, tænkte jeg, at det måske var på tide at teste mig selv lidt.

Opsætning

Jeg har SQL Server 2019 CTP 3.2 installeret på min virtuelle maskine, som har 8 GB hukommelse (maks. serverhukommelse indstillet til 6 GB) og 4 vCPU'er. Jeg oprettede fire (4) tempdb-datafiler, hver størrelse til 1 GB.

Jeg gendannede en kopi af WideWorldImporters og oprettede derefter tre lagrede procedurer (definitioner nedenfor). Hver lagret procedure accepterer en datoinput og skubber alle rækker fra Sales.Order og Sales.OrderLines for den pågældende dato ind i det midlertidige objekt. I Sales.usp_OrderInfoTV er objektet en tabelvariabel, i Sales.usp_OrderInfoTT er objektet en midlertidig tabel defineret via SELECT … INTO med en nonclustered tilføjet efterfølgende, og i Sales.usp_OrderInfoTTALT er objektet en foruddefineret midlertidig tabel, som derefter ændres at have en ekstra kolonne. Når dataene er føjet til det midlertidige objekt, er der en SELECT-sætning mod objektet, der slutter sig til tabellen Sales.Kunder.

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

Standardadfærden for SQL Server 2019 er, at tempdb-metadataene ikke er hukommelsesoptimerede, og vi kan bekræfte dette ved at tjekke sys.configurations:

  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;

For alle tre lagrede procedurer vil vi bruge sqlcmd til at generere 20 samtidige tråde, der kører en af ​​to forskellige .sql-filer. Den første .sql-fil, som vil blive brugt af 19 tråde, vil udføre proceduren i en løkke 1000 gange. Den anden .sql-fil, som kun vil have én (1) tråd, vil udføre proceduren i en løkke 3000 gange. Filen inkluderer også TSQL for at fange to målinger af interesse:samlet varighed og ventestatistikker. Vi vil bruge Query Store til at registrere den gennemsnitlige varighed for 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

Eksempel på kommandolinjefil:

Resultater

Efter at have udført kommandolinjefilerne, der genererer 20 tråde for hver lagret procedure, viser kontrol af den samlede varighed for de 12.000 udførelser af hver procedure følgende:

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

De lagrede procedurer med de midlertidige tabeller (usp_OrderInfoTT og usp_OrderInfoTTC) tog længere tid at fuldføre. Hvis vi ser på individuelle forespørgselsydelser:

  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, at SELECT ... INTO for usp_OrderInfoTT tog omkring 28 ms i gennemsnit (varigheden i Query Store er gemt i mikrosekunder), og kun tog 9 ms, da den midlertidige tabel blev oprettet på forhånd. For tabelvariablen tog INSERT lidt over 22ms i gennemsnit. Interessant nok tog SELECT-forespørgslen lidt over 1 ms for de midlertidige tabeller og ca. 2,7 ms for tabelvariablen.

En kontrol af ventestatistikdata finder en velkendt wait_type, PAGELATCH*:

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

Bemærk, at vi kun ser PAGELATCH* venter på test 1 og 2, som var procedurerne med de midlertidige tabeller. For usp_OrderInfoTV, som brugte en tabelvariabel, ser vi kun SOS_SCHEDULER_YIELD-venter. Bemærk venligst: Dette betyder på ingen måde, at du skal bruge tabelvariabler i stedet for midlertidige tabeller , og det betyder heller ikke, at du ikke vil har PAGELATCH-venter med tabelvariabler. Dette er et konstrueret scenarie; Jeg højt anbefaler, at du tester med DIN kode for at se, hvilke wait_types der vises.

Nu vil vi ændre forekomsten til at bruge hukommelsesoptimerede tabeller til tempdb-metadataene. Der er to måder dette kan gøres på, via kommandoen ALTER SERVER CONFIGURATION eller ved at bruge sp_configure. Da denne indstilling er en avanceret mulighed, skal du først aktivere avancerede indstillinger, hvis du bruger sp_configure.

ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Efter denne ændring er det nødvendigt at genstarte forekomsten. (BEMÆRK:du kan ændre dette tilbage til IKKE at bruge hukommelsesoptimerede tabeller, du skal bare genstarte instansen igen.) Efter genstarten, hvis vi tjekker sys.configurations igen, kan vi se, at metadatatabellerne er hukommelsesoptimerede:

Efter at have udført kommandolinjefilerne igen, viser den samlede varighed for de 21.000 afviklinger af hver procedure følgende (bemærk, at resultaterne er sorteret efter lagret procedure for nemmere sammenligning):

Der var helt sikkert en forbedring i ydeevnen for både usp_OrderInfoTT og usp_OrderInfoTTC , og en lille stigning i ydeevnen for usp_OrderInfoTV. Lad os tjekke forespørgslens varighed:

For alle forespørgsler er forespørgselsvarigheden næsten den samme, bortset fra stigningen i INSERT-varigheden, når tabellen er præ-oprettet, hvilket er fuldstændig uventet. Vi ser en interessant ændring i ventestatistikken:

For usp_OrderInfoTT udføres en SELECT … INTO for at oprette den midlertidige tabel. Ventetiden ændres fra at være PAGELATCH_EX og PAGELATCH_SH til kun PAGELATCH_EX og SOS_SCHEDULER_YIELD. Vi ser ikke længere PAGELATCH_SH-venterne.

For usp_OrderInfoTTC, som opretter den midlertidige tabel og derefter indsætter, vises PAGELATCH_EX og PAGELATCH_SH ventetider ikke længere, og vi ser kun SOS_SCHEDULER_YIELD venter.

Endelig, for OrderInfoTV, er ventetiden konsekvente – kun SOS_SCHEDULER_YIELD, med næsten den samme samlede ventetid.

Oversigt

Baseret på denne test ser vi en forbedring i alle tilfælde, markant for de lagrede procedurer med midlertidige tabeller. Der er en lille ændring for tabelvariablen. Det er ekstremt vigtigt at huske, at dette er et scenarie med en lille belastningstest. Jeg var meget interesseret i at prøve disse tre meget simple scenarier, for at prøve at forstå, hvad der kunne have mest gavn af at gøre tempdb-metadatahukommelsen optimeret. Denne arbejdsbyrde var lille og kørte i meget begrænset tid – faktisk havde jeg mere varierede resultater med flere tråde, hvilket er værd at undersøge i et andet indlæg. Den største fordel er, at som med alle nye funktioner og funktionalitet, er test vigtig. For denne funktion vil du have en basislinje for den aktuelle ydeevne, som du kan sammenligne metrics som f.eks. Batch-anmodninger/Sec og ventestatistikker efter at have gjort metadatahukommelsen optimeret.

Yderligere overvejelser

Brug af In-Memory OLTP kræver en filgruppe af typen MEMORY OPTIMIZED DATA. Efter aktivering af MEMORY_OPTIMIZED TEMPDB_METADATA oprettes der dog ingen yderligere filgruppe for tempdb. Derudover vides det ikke, om de hukommelsesoptimerede tabeller er holdbare (SCHEMA_AND_DATA) eller ej (SCHEMA_ONLY). Typisk kan dette bestemmes via sys.tables (durability_desc), men intet returnerer for de involverede systemtabeller, når du forespørger dette i tempdb, selv når du bruger den dedikerede administratorforbindelse. Du har mulighed for at se ikke-klyngede indekser for de hukommelsesoptimerede tabeller. Du kan bruge følgende forespørgsel til at se, hvilke tabeller der er hukommelsesoptimerede 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 derefter sp_helpindex for enhver af tabellerne, for eksempel:

EXEC sys.sp_helpindex N'sys.sysobjvalues';

Bemærk, at hvis det er et hash-indeks (som kræver estimering af BUCKET_COUNT som en del af oprettelsen), vil beskrivelsen indeholde "ikke-klyngede hash."


  1. Knee-Jerk Performance Tuning:Forkert brug af midlertidige borde

  2. Hvordan Substr() virker i SQLite

  3. Vigtigste teknologiændringer i E-Business Suite 12.2

  4. Min PostgreSQL-database er løbet tør for diskplads