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

Khắc phục sự cố Cấp bộ nhớ có thể thay đổi trong SQL Server

Một trong những vấn đề phức tạp hơn cần khắc phục trong SQL Server có thể là những vấn đề liên quan đến cấp bộ nhớ. Một số truy vấn cần nhiều bộ nhớ hơn những truy vấn khác để thực thi, dựa trên những thao tác cần được thực hiện (ví dụ:sắp xếp, băm). Trình tối ưu hóa của SQL Server ước tính lượng bộ nhớ cần thiết và truy vấn phải được cấp bộ nhớ để bắt đầu thực thi. Nó giữ khoản trợ cấp đó trong suốt thời gian thực thi truy vấn - có nghĩa là nếu trình tối ưu hóa đánh giá quá cao bộ nhớ, bạn có thể gặp phải các vấn đề đồng thời. Nếu nó đánh giá thấp bộ nhớ, thì bạn có thể thấy tràn trong tempdb. Không phải là lý tưởng và khi bạn chỉ cần có quá nhiều truy vấn yêu cầu nhiều bộ nhớ hơn khả năng có thể cấp, bạn sẽ thấy RESOURCE_SEMAPHORE đang đợi. Có nhiều cách để tấn công vấn đề này và một trong những phương pháp yêu thích mới của tôi là sử dụng Query Store.

Thiết lập

Chúng tôi sẽ sử dụng một bản sao của WideWorldImporters mà tôi đã tăng lên bằng cách sử dụng thủ tục lưu trữ DataLoadSimulation.DailyProcessToCreateHistory. Bảng Sales.Orders có khoảng 4,6 triệu hàng và bảng Sales.OrderLines có khoảng 9,2 triệu hàng. Chúng tôi sẽ khôi phục bản sao lưu và kích hoạt Cửa hàng truy vấn, đồng thời xóa mọi dữ liệu Cửa hàng truy vấn cũ để chúng tôi không thay đổi bất kỳ số liệu nào cho bản trình diễn này.

Nhắc nhở:Không chạy ALTER DATABASE SET QUERY_STORE CLEAR; chống lại cơ sở dữ liệu sản xuất của bạn trừ khi bạn muốn xóa mọi thứ khỏi Cửa hàng truy vấn.

  USE [master];
  GO
 
  RESTORE DATABASE [WideWorldImporters] 
  	FROM  DISK = N'C:\Backups\WideWorldImporters.bak' WITH  FILE = 1,  
  	MOVE N'WWI_Primary' TO N'C:\Databases\WideWorldImporters\WideWorldImporters.mdf',  
  	MOVE N'WWI_UserData' TO N'C:\Databases\WideWorldImporters\WideWorldImporters_UserData.ndf',  
  	MOVE N'WWI_Log' TO N'C:\Databases\WideWorldImporters\WideWorldImporters.ldf',  
  	NOUNLOAD,  REPLACE,  STATS = 5
  GO
 
  ALTER DATABASE [WideWorldImporters] SET QUERY_STORE = ON;
  GO
 
  ALTER DATABASE [WideWorldImporters] SET QUERY_STORE (
  	OPERATION_MODE = READ_WRITE, INTERVAL_LENGTH_MINUTES = 10
  	);
  GO
 
  ALTER DATABASE [WideWorldImporters] SET QUERY_STORE CLEAR;
  GO

Quy trình được lưu trữ mà chúng tôi sẽ sử dụng để kiểm tra các truy vấn đối với các bảng Đơn hàng và Dòng đơn hàng đã nói ở trên dựa trên phạm vi ngày:

  USE [WideWorldImporters];
  GO
 
  DROP PROCEDURE IF EXISTS [Sales].[usp_OrderInfo_OrderDate];
  GO
 
  CREATE PROCEDURE [Sales].[usp_OrderInfo_OrderDate]
  	@StartDate DATETIME,
  	@EndDate DATETIME
  AS
  SELECT
  	[o].[CustomerID],
  	[o].[OrderDate],
  	[o].[ContactPersonID],
  	[ol].[Quantity]
  FROM [Sales].[Orders] [o]
  JOIN [Sales].[OrderLines] [ol]
  	ON [o].[OrderID] = [ol].[OrderID]
  WHERE [OrderDate] BETWEEN @StartDate AND @EndDate
  ORDER BY [OrderDate];
  GO

Thử nghiệm

Chúng tôi sẽ thực hiện thủ tục được lưu trữ với ba bộ thông số đầu vào khác nhau:

  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-01-08';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-06-30';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-12-31';
  GO

Lần thực thi đầu tiên trả về 1958 hàng, lần thứ hai trả về 267,268 hàng và lần cuối cùng trả về hơn 2,2 triệu hàng. Nếu bạn nhìn vào phạm vi ngày, điều này không có gì đáng ngạc nhiên - phạm vi ngày càng lớn thì dữ liệu được trả về càng nhiều.

Bởi vì đây là một thủ tục được lưu trữ, các tham số đầu vào được sử dụng ban đầu xác định kế hoạch, cũng như bộ nhớ được cấp. Nếu chúng ta nhìn vào kế hoạch thực thi thực tế cho lần thực thi đầu tiên, chúng ta sẽ thấy các vòng lặp lồng nhau và mức cấp bộ nhớ 2656 KB.

Các lần thực thi tiếp theo có cùng một kế hoạch (đó là những gì đã được lưu trong bộ nhớ cache) và cùng một mức cấp bộ nhớ, nhưng chúng tôi nhận được manh mối là chưa đủ vì có một cảnh báo sắp xếp.

Nếu chúng ta xem trong Cửa hàng truy vấn cho thủ tục được lưu trữ này, chúng ta thấy ba lần thực thi và các giá trị giống nhau cho bộ nhớ usedKB, cho dù chúng ta xem xét Trung bình, Tối thiểu, Tối đa, Cuối cùng hoặc Độ lệch chuẩn. Lưu ý:thông tin cấp bộ nhớ trong Cửa hàng truy vấn được báo cáo là số trang 8KB.

  SELECT
  	[qst].[query_sql_text],
  	[qsq].[query_id], 
  	[qsp].[plan_id],
  	[qsq].[object_id],
  	[rs].[count_executions],
  	[rs].[last_execution_time],
  	[rs].[avg_duration],
  	[rs].[avg_logical_io_reads],
  	[rs].[avg_query_max_used_memory] * 8 AS [AvgUsedKB],
  	[rs].[min_query_max_used_memory] * 8 AS [MinUsedKB], 
  	  --memory grant (reported as the number of 8 KB pages) for the query plan within the aggregation interval
  	[rs].[max_query_max_used_memory] * 8 AS [MaxUsedKB],
  	[rs].[last_query_max_used_memory] * 8 AS [LastUsedKB],
  	[rs].[stdev_query_max_used_memory] * 8 AS [StDevUsedKB],
  	TRY_CONVERT(XML, [qsp].[query_plan]) AS [QueryPlan_XML]
  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(N'Sales.usp_OrderInfo_OrderDate');

Nếu chúng tôi đang tìm kiếm các vấn đề về cấp bộ nhớ trong trường hợp này - trong đó một gói được lưu vào bộ nhớ cache và sử dụng lại - Cửa hàng truy vấn sẽ không giúp được chúng tôi.

Nhưng điều gì sẽ xảy ra nếu truy vấn cụ thể được biên dịch khi thực thi, vì gợi ý RECOMPILE hoặc vì nó đặc biệt?

Chúng tôi có thể thay đổi quy trình để thêm gợi ý RECOMPILE vào câu lệnh (được khuyến nghị khi thêm RECOMPILE ở cấp thủ tục hoặc chạy thủ tục VỚI RECOMIPLE):

  ALTER PROCEDURE [Sales].[usp_OrderInfo_OrderDate]
  	@StartDate DATETIME,
  	@EndDate DATETIME
  AS
  SELECT
  	[o].[CustomerID],
  	[o].[OrderDate],
  	[o].[ContactPersonID],
  	[ol].[Quantity]
  FROM [Sales].[Orders] [o]
  JOIN [Sales].[OrderLines] [ol]
  	ON [o].[OrderID] = [ol].[OrderID]
  WHERE [OrderDate] BETWEEN @StartDate AND @EndDate
  ORDER BY [OrderDate]
  OPTION (RECOMPILE);
  GO

Bây giờ chúng ta sẽ chạy lại thủ tục của mình với các tham số đầu vào giống như trước đây và kiểm tra kết quả đầu ra:

Lưu ý rằng chúng tôi có một query_id mới - văn bản truy vấn đã thay đổi vì chúng tôi đã thêm TÙY CHỌN (RECOMPILE) vào nó - và chúng tôi cũng có hai giá trị plan_id mới và chúng tôi có các số cấp bộ nhớ khác nhau cho một trong các kế hoạch của chúng tôi. Đối với plan_id 5, chỉ có một lần thực thi và các số cấp bộ nhớ khớp với lần thực thi ban đầu - vì vậy kế hoạch đó dành cho phạm vi ngày nhỏ. Hai phạm vi ngày lớn hơn tạo ra cùng một kế hoạch, nhưng có sự thay đổi đáng kể trong mức cấp bộ nhớ - tối thiểu là 94.528 và tối đa là 573.568.

Nếu chúng ta xem xét thông tin cấp bộ nhớ bằng cách sử dụng báo cáo Cửa hàng truy vấn, thì biến thể này hiển thị hơi khác một chút. Mở báo cáo Người tiêu dùng tài nguyên hàng đầu từ cơ sở dữ liệu, sau đó thay đổi số liệu thành Mức tiêu thụ bộ nhớ (KB) và Trung bình, truy vấn của chúng tôi với RECOMPILE xuất hiện ở đầu danh sách.

Trong cửa sổ này, các chỉ số được tổng hợp theo truy vấn, không phải kế hoạch. Truy vấn mà chúng tôi đã thực thi trực tiếp đối với các dạng xem Cửa hàng truy vấn được liệt kê không chỉ là query_id mà còn cả plan_id. Tại đây, chúng tôi có thể thấy rằng truy vấn có hai kế hoạch và chúng tôi có thể xem cả hai kế hoạch trong cửa sổ tóm tắt kế hoạch, nhưng các chỉ số được kết hợp cho tất cả các kế hoạch trong chế độ xem này.

Sự thay đổi trong việc cấp bộ nhớ là rõ ràng khi chúng ta đang xem xét trực tiếp các chế độ xem. Chúng tôi có thể tìm các truy vấn có sự thay đổi bằng cách sử dụng Giao diện người dùng bằng cách thay đổi Thống kê từ Trung bình thành StDev:

Chúng ta có thể tìm thấy thông tin tương tự bằng cách truy vấn các chế độ xem Cửa hàng truy vấn và sắp xếp theo stdev_query_max_used_memory giảm dần. Tuy nhiên, chúng tôi cũng có thể tìm kiếm dựa trên sự khác biệt giữa mức cấp bộ nhớ tối thiểu và tối đa hoặc tỷ lệ phần trăm của sự khác biệt. Ví dụ:nếu chúng tôi lo lắng về các trường hợp chênh lệch trong các khoản tài trợ lớn hơn 512MB, chúng tôi có thể chạy:

  SELECT
  	[qst].[query_sql_text],
  	[qsq].[query_id], 
  	[qsp].[plan_id],
  	[qsq].[object_id],
  	[rs].[count_executions],
  	[rs].[last_execution_time],
  	[rs].[avg_duration],
  	[rs].[avg_logical_io_reads],
  	[rs].[avg_query_max_used_memory] * 8 AS [AvgUsedKB],
  	[rs].[min_query_max_used_memory] * 8 AS [MinUsedKB], 
  	[rs].[max_query_max_used_memory] * 8 AS [MaxUsedKB],
  	[rs].[last_query_max_used_memory] * 8 AS [LastUsedKB],
  	[rs].[stdev_query_max_used_memory] * 8 AS [StDevUsedKB],
  	TRY_CONVERT(XML, [qsp].[query_plan]) AS [QueryPlan_XML]
  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 ([rs].[max_query_max_used_memory]*8) - ([rs].[min_query_max_used_memory]*8) > 524288;

Những người bạn đang chạy SQL Server 2017 với chỉ mục Columnstore, những người có lợi thế về phản hồi Cấp bộ nhớ, cũng có thể sử dụng thông tin này trong Cửa hàng truy vấn. Trước tiên, chúng tôi sẽ thay đổi bảng Đơn hàng của mình để thêm một chỉ mục Columnstore theo nhóm:

  ALTER TABLE [Sales].[Invoices] DROP CONSTRAINT [FK_Sales_Invoices_OrderID_Sales_Orders];
  GO
 
  ALTER TABLE [Sales].[Orders] DROP CONSTRAINT [FK_Sales_Orders_BackorderOrderID_Sales_Orders];
  GO
 
  ALTER TABLE [Sales].[OrderLines] DROP CONSTRAINT [FK_Sales_OrderLines_OrderID_Sales_Orders];
  GO
 
  ALTER TABLE [Sales].[Orders] DROP CONSTRAINT [PK_Sales_Orders] WITH ( ONLINE = OFF );
  GO
 
  CREATE CLUSTERED COLUMNSTORE INDEX CCI_Orders
  ON [Sales].[Orders];

Sau đó, chúng tôi sẽ đặt chế độ kết hợp cơ sở dữ liệu thành 140 để chúng tôi có thể tận dụng phản hồi cấp bộ nhớ:

  ALTER DATABASE [WideWorldImporters] SET COMPATIBILITY_LEVEL = 140;
  GO

Cuối cùng, chúng tôi sẽ thay đổi quy trình đã lưu trữ của mình để xóa TÙY CHỌN (RECOMPILE) khỏi truy vấn của chúng tôi và sau đó chạy nó một vài lần với các giá trị đầu vào khác nhau:

  ALTER PROCEDURE [Sales].[usp_OrderInfo_OrderDate]
  	@StartDate DATETIME,
  	@EndDate DATETIME
  AS
  SELECT
  	[o].[CustomerID],
  	[o].[OrderDate],
  	[o].[ContactPersonID],
  	[ol].[Quantity]
  FROM [Sales].[Orders] [o]
  JOIN [Sales].[OrderLines] [ol]
  	ON [o].[OrderID] = [ol].[OrderID]
  WHERE [OrderDate] BETWEEN @StartDate AND @EndDate
  ORDER BY [OrderDate];
  GO 
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-01-08';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-06-30';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-12-31';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-06-30';
  GO
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-01-08';
  GO 
 
  EXEC [Sales].[usp_OrderInfo_OrderDate] '2016-01-01', '2016-12-31';
  GO

Trong Query Store, chúng tôi thấy những điều sau:

Chúng tôi có một kế hoạch mới cho query_id =1, có các giá trị khác nhau cho các chỉ số cấp bộ nhớ và StDev thấp hơn một chút so với kế hoạch mà chúng tôi đã có với plan_id 6. Nếu chúng tôi nhìn vào kế hoạch trong Query Store, chúng tôi thấy rằng nó truy cập vào chỉ mục Columnstore được phân nhóm :

Hãy nhớ rằng kế hoạch trong Cửa hàng truy vấn là kế hoạch đã được thực thi, nhưng nó chỉ chứa các ước tính. Mặc dù gói trong bộ đệm ẩn của gói có thông tin cấp bộ nhớ được cập nhật khi phản hồi về bộ nhớ xảy ra, thông tin này không được áp dụng cho gói hiện có trong Cửa hàng truy vấn.

Tóm tắt

Đây là điều tôi thích khi sử dụng Cửa hàng truy vấn để xem các truy vấn có cấp bộ nhớ có thể thay đổi:dữ liệu sẽ tự động được thu thập. Nếu sự cố này xuất hiện bất ngờ, chúng tôi không cần phải đặt bất cứ thứ gì để thử và thu thập thông tin, chúng tôi đã nắm bắt được nó trong Cửa hàng truy vấn. Trong trường hợp truy vấn được tham số hóa, có thể khó tìm ra sự thay đổi cấp bộ nhớ do có thể xảy ra các giá trị tĩnh vì bộ nhớ đệm kế hoạch. Tuy nhiên, chúng tôi cũng có thể phát hiện ra rằng, do biên dịch lại, truy vấn có nhiều kế hoạch với các giá trị cấp bộ nhớ cực kỳ khác nhau mà chúng tôi có thể sử dụng để theo dõi sự cố. Có nhiều cách khác nhau để điều tra sự cố bằng cách sử dụng dữ liệu được thu thập trong Cửa hàng truy vấn và nó cho phép bạn xem xét sự cố một cách chủ động cũng như phản ứng lại.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tách các giá trị được phân tách trong một cột SQL thành nhiều hàng

  2. Cơ bản và cách sử dụng gợi ý NOLOCK trong SQL Server

  3. Quản lý giao dịch đồng thời bằng cách sử dụng khóa trong SQL Server

  4. Cột được tính toán trong SQL Server là gì?

  5. Cách hiển thị đối chiếu của một cột trong SQL Server (T-SQL)