Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Các cải tiến tempdb trong SQL Server 2019

Tôi đã đưa ra các khuyến nghị tương tự về tempdb kể từ khi tôi bắt đầu làm việc với SQL Server hơn 15 năm trước, khi tôi làm việc với khách hàng đang chạy phiên bản 2000. Ý chính của nó:tạo nhiều tệp dữ liệu có kích thước giống nhau, với cùng một chế độ tự động -cài đặt tăng trưởng, bật cờ theo dõi 1118 (và có thể là 1117) và giảm việc sử dụng tempdb của bạn. Từ phía khách hàng, đây là giới hạn của những gì có thể được thực hiện *, cho đến SQL Server 2019.

* Có một vài đề xuất mã hóa bổ sung mà Pam Lahoud thảo luận trong bài đăng rất nhiều thông tin của cô ấy, TEMPDB - TEMPDB - Files and Trace Flags and Updates, Oh My!

Điều tôi thấy thú vị là, sau ngần ấy thời gian, tempdb vẫn là một vấn đề. Nhóm SQL Server đã thực hiện nhiều thay đổi trong nhiều năm để cố gắng và giảm thiểu sự cố, nhưng tình trạng lạm dụng vẫn tiếp diễn. Phiên bản mới nhất của nhóm SQL Server đang di chuyển bảng hệ thống (siêu dữ liệu) cho tempdb sang OLTP trong bộ nhớ (hay còn gọi là tối ưu hóa bộ nhớ). Một số thông tin có sẵn trong ghi chú phát hành SQL Server 2019 và có một bản demo từ Bob Ward và Conor Cunningham trong ngày đầu tiên của bài phát biểu PASS Summit. Pam Lahoud cũng đã thực hiện một bản demo nhanh trong phiên họp chung của PASS Summit. Bây giờ CTP 3.2 2019 đã ra mắt, tôi nghĩ có lẽ đã đến lúc tự mình kiểm tra một chút.

Thiết lập

Tôi đã cài đặt SQL Server 2019 CTP 3.2 trên máy ảo của mình, máy có 8GB bộ nhớ (bộ nhớ máy chủ tối đa được đặt thành 6 GB) và 4 vCPU. Tôi đã tạo bốn (4) tệp dữ liệu tempdb, mỗi tệp có kích thước là 1GB.

Tôi đã khôi phục một bản sao của WideWorldImporters và sau đó tạo ba thủ tục được lưu trữ (định nghĩa bên dưới). Mỗi thủ tục được lưu trữ chấp nhận một đầu vào ngày tháng và đẩy tất cả các hàng từ Sales.Order và Sales.OrderLines cho ngày đó vào đối tượng tạm thời. Trong Sales.usp_OrderInfoTV, đối tượng là một biến bảng, trong Sales.usp_OrderInfoTT, đối tượng là một bảng tạm thời được xác định thông qua SELECT… INTO với một không bao gồm được thêm vào sau đó và trong Sales.usp_OrderInfoTTALT, đối tượng là một bảng tạm thời được xác định trước, sau đó được thay đổi để có một cột bổ sung. Sau khi dữ liệu được thêm vào đối tượng tạm thời, có một câu lệnh SELECT chống lại đối tượng tham gia vào bảng 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

Thử nghiệm

Hành vi mặc định cho SQL Server 2019 là siêu dữ liệu tempdb không được tối ưu hóa bộ nhớ và chúng tôi có thể xác nhận điều này bằng cách kiểm tra sys.configurations:

  SELECT *
  FROM sys.configurations
  WHERE configuration_id = 1589;

Đối với tất cả ba thủ tục được lưu trữ, chúng tôi sẽ sử dụng sqlcmd để tạo 20 luồng đồng thời chạy một trong hai tệp .sql khác nhau. Tệp .sql đầu tiên, sẽ được sử dụng bởi 19 luồng, sẽ thực hiện quy trình trong một vòng lặp 1000 lần. Tệp .sql thứ hai, chỉ có một (1) luồng, sẽ thực hiện thủ tục trong một vòng lặp 3000 lần. Tệp cũng bao gồm TSQL để nắm bắt hai số liệu quan tâm:tổng thời lượng và thống kê chờ. Chúng tôi sẽ sử dụng Cửa hàng truy vấn để nắm bắt thời lượng trung bình cho quy trình.

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

Ví dụ về tệp dòng lệnh:

Kết quả

Sau khi thực thi các tệp dòng lệnh tạo ra 20 luồng cho mỗi thủ tục được lưu trữ, việc kiểm tra tổng thời lượng cho 12.000 lần thực thi của mỗi thủ tục cho thấy như sau:

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

Các thủ tục được lưu trữ với các bảng tạm thời (usp_OrderInfoTT và usp_OrderInfoTTC) mất nhiều thời gian hơn để hoàn thành. Nếu chúng ta xem xét hiệu suất truy vấn riêng lẻ:

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

Chúng ta có thể thấy rằng SELECT… INTO cho usp_OrderInfoTT mất trung bình khoảng 28 mili giây (thời lượng trong Cửa hàng truy vấn được lưu trữ bằng micro giây) và chỉ mất 9 mili giây khi bảng tạm thời được tạo trước. Đối với biến bảng, INSERT chỉ mất hơn 22ms trung bình. Thật thú vị, truy vấn SELECT chỉ mất hơn 1 mili giây cho các bảng tạm thời và khoảng 2,7 mili giây cho biến bảng.

Kiểm tra dữ liệu thống kê chờ tìm thấy loại wait_type quen thuộc, PAGELATCH *:

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

Lưu ý rằng chúng ta chỉ thấy PAGELATCH * đang đợi các thử nghiệm 1 và 2, là các thủ tục với các bảng tạm thời. Đối với usp_OrderInfoTV, đã sử dụng một biến bảng, chúng tôi chỉ thấy SOS_SCHEDULER_YIELD chờ đợi. Xin lưu ý: Theo bất kỳ cách nào, điều này không ngụ ý rằng bạn nên sử dụng các biến bảng thay vì các bảng tạm thời , cũng không ngụ ý rằng bạn sẽ không có PAGELATCH chờ với các biến bảng. Đây là một kịch bản giả tạo; Tôi rất cao khuyên bạn nên kiểm tra với mã CỦA BẠN để xem loại wait_types nào xuất hiện.

Bây giờ chúng ta sẽ thay đổi phiên bản để sử dụng các bảng được tối ưu hóa bộ nhớ cho siêu dữ liệu tempdb. Có hai cách để thực hiện việc này, thông qua lệnh ALTER SERVER CONFIGURATION hoặc bằng cách sử dụng sp_configure. Vì cài đặt này là một tùy chọn nâng cao, nếu bạn sử dụng sp_configure, trước tiên, bạn cần bật các tùy chọn nâng cao.

ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON;
GO

Sau thay đổi này, cần phải khởi động lại phiên bản. (LƯU Ý:bạn có thể thay đổi điều này trở lại KHÔNG sử dụng các bảng được tối ưu hóa bộ nhớ, bạn chỉ cần khởi động lại phiên bản một lần nữa.) Sau khi khởi động lại, nếu chúng tôi kiểm tra lại cấu hình sys.configu, chúng tôi có thể thấy các bảng siêu dữ liệu được tối ưu hóa bộ nhớ:

Sau khi thực thi lại các tệp dòng lệnh, tổng thời lượng cho 21.000 lần thực thi của mỗi thủ tục sẽ hiển thị như sau (lưu ý rằng kết quả được sắp xếp theo thủ tục được lưu trữ để so sánh dễ dàng hơn):

Chắc chắn đã có sự cải thiện về hiệu suất cho cả usp_OrderInfoTT và usp_OrderInfoTTC, đồng thời tăng nhẹ hiệu suất cho usp_OrderInfoTV. Hãy kiểm tra thời lượng truy vấn:

Đối với tất cả các truy vấn, thời lượng truy vấn gần như nhau, ngoại trừ việc tăng thời lượng INSERT khi bảng được tạo trước, điều này hoàn toàn không mong muốn. Chúng tôi nhận thấy một sự thay đổi thú vị trong số liệu thống kê về thời gian chờ:

Đối với usp_OrderInfoTT, lệnh SELECT… INTO được thực thi để tạo bảng tạm thời. Thời gian chờ thay đổi từ PAGELATCH_EX và PAGELATCH_SH thành chỉ PAGELATCH_EX SOS_SCHEDULER_YIELD. Chúng tôi không còn thấy PAGELATCH_SH đang đợi.

Đối với usp_OrderInfoTTC, tạo bảng tạm thời và sau đó chèn, PAGELATCH_EX và PAGELATCH_SH chờ không còn xuất hiện nữa và chúng tôi chỉ thấy SOS_SCHEDULER_YIELD chờ.

Cuối cùng, đối với OrderInfoTV, thời gian chờ là nhất quán - chỉ SOS_SCHEDULER_YIELD, với tổng thời gian chờ gần như bằng nhau.

Tóm tắt

Dựa trên thử nghiệm này, chúng tôi thấy có sự cải thiện trong mọi trường hợp, đáng kể đối với các thủ tục được lưu trữ với các bảng tạm thời. Có một chút thay đổi đối với thủ tục biến bảng. Điều cực kỳ quan trọng cần nhớ rằng đây là một tình huống, với một thử nghiệm tải nhỏ. Tôi rất muốn thử ba tình huống rất đơn giản này, để thử và hiểu điều gì có thể có lợi nhất từ ​​việc tối ưu hóa bộ nhớ siêu dữ liệu tempdb. Khối lượng công việc này nhỏ và chạy trong một thời gian rất hạn chế - trên thực tế, tôi đã có nhiều kết quả đa dạng hơn với nhiều chủ đề hơn, đáng để khám phá trong một bài đăng khác. Bước tiến lớn nhất là, cũng như với tất cả các tính năng và chức năng mới, việc thử nghiệm là rất quan trọng. Đối với tính năng này, bạn muốn có đường cơ sở về hiệu suất hiện tại để so sánh các chỉ số như Yêu cầu hàng loạt / giây và thống kê chờ sau khi thực hiện tối ưu hóa bộ nhớ siêu dữ liệu.

Cân nhắc bổ sung

Sử dụng OLTP trong bộ nhớ yêu cầu nhóm tệp thuộc loại DỮ LIỆU ĐƯỢC TỐI ƯU HÓA BỘ NHỚ. Tuy nhiên, sau khi bật MEMORY_OPTIMIZED TEMPDB_METADATA, không có nhóm tệp bổ sung nào được tạo cho tempdb. Ngoài ra, không biết liệu các bảng được tối ưu hóa bộ nhớ có bền (SCHEMA_AND_DATA) hay không (SCHEMA_ONLY). Thông thường, điều này có thể được xác định thông qua sys.tables (bền_desc), nhưng không có gì trả về cho các bảng hệ thống liên quan khi truy vấn điều này trong tempdb, ngay cả khi sử dụng Kết nối quản trị viên chuyên dụng. Bạn có khả năng xem các chỉ mục không phân biệt cho các bảng được tối ưu hóa bộ nhớ. Bạn có thể sử dụng truy vấn sau để xem bảng nào được tối ưu hóa bộ nhớ trong 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;

Sau đó, đối với bất kỳ bảng nào, hãy chạy sp_helpindex, ví dụ:

EXEC sys.sp_helpindex N'sys.sysobjvalues';

Lưu ý rằng nếu đó là chỉ mục băm (yêu cầu ước tính BUCKET_COUNT như một phần của quá trình tạo), thì mô tả sẽ bao gồm “băm không phân biệt.”


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL đệ quy CTE:Tìm các đối tượng được liên kết bởi thuộc tính

  2. Thủ thuật nhanh và tốt nhất để khôi phục tệp SQL Server MDF

  3. Điền vào ngày bị thiếu cho đầu ra truy vấn máy chủ SQL bằng CTE

  4. Không thể tìm thấy microsoft.sqlserver.batchparser.dll

  5. Ví dụ về Chuyển đổi ‘smalldatetime’ thành ‘datetime’ trong SQL Server (T-SQL)