sql >> Databasteknik >  >> RDS >> Sqlserver

Felsökning av variabelt minnesbidrag i SQL Server

Ett av de mer förbryllande problemen att felsöka i SQL Server kan vara de som är relaterade till minnesbidrag. Vissa frågor behöver mer minne än andra för att utföra, baserat på vilka operationer som behöver utföras (t.ex. sortering, hash). SQL Servers optimerare uppskattar hur mycket minne som behövs, och frågan måste erhålla minnesanslaget för att börja exekvera. Det gäller det bidraget så länge frågan körs – vilket innebär att om optimeraren överskattar minnet kan du stöta på samtidiga problem. Om det underskattar minnet kan du se spill i tempdb. Ingetdera är idealiskt, och när du helt enkelt har för många frågor som kräver mer minne än vad som är tillgängligt att bevilja, kommer du att se RESOURCE_SEMAPHORE väntar. Det finns flera sätt att attackera det här problemet, och en av mina nya favoritmetoder är att använda Query Store.

Inställningar

Vi kommer att använda en kopia av WideWorldImporters som jag blåste upp med den lagrade proceduren DataLoadSimulation.DailyProcessToCreateHistory. Tabellen Sales.Orders har cirka 4,6 miljoner rader och tabellen Sales.OrderLines har cirka 9,2 miljoner rader. Vi kommer att återställa säkerhetskopian och aktivera Query Store och rensa bort alla gamla Query Store-data så att vi inte ändrar några mätvärden för den här demon.

Påminnelse:Kör inte ALTER DATABASE SET QUERY_STORE CLEAR; mot din produktionsdatabas om du inte vill ta bort allt från Query Store.

  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

Den lagrade proceduren som vi kommer att använda för att testa förfrågningar ovannämnda Order- och OrderLines-tabeller baserat på ett datumintervall:

  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

Testning

Vi kommer att utföra den lagrade proceduren med tre olika uppsättningar av inmatningsparametrar:

  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

Den första exekveringen returnerar 1958 rader, den andra returnerar 267 268 rader och den sista returnerar över 2,2 miljoner rader. Om du tittar på datumintervallen är detta inte förvånande – ju större datumintervall desto mer data returneras.

Eftersom detta är en lagrad procedur bestämmer de ingångsparametrar som används initialt planen, såväl som minnet som ska beviljas. Om vi ​​tittar på den faktiska exekveringsplanen för den första exekveringen ser vi kapslade slingor och ett minnesanslag på 2656 KB.

Efterföljande avrättningar har samma plan (eftersom det var det som cachelagrades) och samma minnesanslag, men vi får en aning om att det inte räcker eftersom det finns en slags varning.

Om vi ​​letar efter denna lagrade procedur i Query Store ser vi tre exekveringar och samma värden för UsedKB-minne, oavsett om vi tittar på Average, Minimum, Maximum, Last eller Standard Deviation. Obs:information om minnesbidrag i Query Store rapporteras som antalet 8KB-sidor.

  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');

Om vi ​​letar efter problem med minnesanslag i det här scenariot – där en plan cachelagras och återanvänds – kommer Query Store inte att hjälpa oss.

Men vad händer om den specifika frågan kompileras vid körning, antingen på grund av en RECOMPILE-tips eller för att den är ad-hoc?

Vi kan ändra proceduren för att lägga till RECOMPILE-tipset till satsen (vilket rekommenderas framför att lägga till RECOMPILE på procedurnivå, eller köra proceduren MED 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

Nu kommer vi att köra om vår procedur med samma ingångsparametrar som tidigare och kontrollera utdata:

Lägg märke till att vi har ett nytt query_id – frågetexten ändrades eftersom vi lade till OPTION (RECOMPILE) till den – och vi har också två nya plan_id-värden, och vi har olika minnesbidragsnummer för en av våra planer. För plan_id 5 finns det bara en exekvering, och minnesbidragsnumren matchar den initiala exekveringen – så den planen är för det lilla datumintervallet. De två större datumintervallen genererade samma plan, men det finns betydande variationer i minnesanslagen – 94 528 för minimum och 573 568 för maximum.

Om vi ​​tittar på information om minnesbidrag med hjälp av Query Store-rapporterna visas denna variation lite annorlunda. Genom att öppna rapporten Toppresurskonsumenter från databasen och sedan ändra måtten till Minnesförbrukning (KB) och Genomsnittlig, hamnar vår fråga med RECOMPILE överst på listan.

I det här fönstret sammanställs mätvärden efter fråga, inte plan. Frågan vi körde direkt mot Query Store-vyerna listade inte bara query_id utan även plan_id. Här kan vi se att frågan har två planer, och vi kan se dem båda i planöversiktsfönstret, men mätvärdena kombineras för alla planer i den här vyn.

Variationen i minnesbidrag är uppenbar när vi tittar direkt på åsikterna. Vi kan hitta frågor med variabilitet med hjälp av användargränssnittet genom att ändra statistiken från Avg till StDev:

Vi kan hitta samma information genom att fråga i Query Store-vyerna och beställa efter stdev_query_max_used_memory fallande. Men vi kan också söka baserat på skillnaden mellan minsta och maximala minnesbidrag, eller en procentandel av skillnaden. Om vi ​​till exempel var oroliga för fall där skillnaden i bidragen var större än 512 MB kunde vi köra:

  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;

De av er som kör SQL Server 2017 med Columnstore-index, som har fördelen av Memory Grant-feedback, kan också använda denna information i Query Store. Vi kommer först att ändra vår ordertabell för att lägga till ett klustrat Columnstore-index:

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

Sedan kommer vi att ställa in databaskombabilitetsläget till 140 så att vi kan utnyttja minnesåterkoppling:

  ALTER DATABASE [WideWorldImporters] SET COMPATIBILITY_LEVEL = 140;
  GO

Slutligen kommer vi att ändra vår lagrade procedur för att ta bort OPTION (RECOMPILE) från vår fråga och sedan köra den några gånger med de olika inmatningsvärdena:

  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

Inom Query Store ser vi följande:

Vi har en ny plan för query_id =1, som har andra värden för minnesgrantningsmåtten, och en något lägre StDev än vi hade med plan_id 6. Om vi ​​tittar i planen i Query Store ser vi att den kommer åt det klustrade Columnstore-indexet :

Kom ihåg att planen i Query Store är den som kördes, men den innehåller bara uppskattningar. Även om planen i planens cache har minnesinformation uppdaterad när minnesfeedback inträffar, tillämpas inte denna information på den befintliga planen i Query Store.

Sammanfattning

Det här är vad jag gillar med att använda Query Store för att titta på frågor med variabla minnesanslag:data samlas in automatiskt. Om det här problemet dyker upp oväntat behöver vi inte sätta något på plats för att försöka samla in information, vi har redan inhämtat det i Query Store. I fallet där en fråga parametriseras kan det vara svårare att hitta variabilitet för minnesbeviljande på grund av risken för statiska värden på grund av plancachelagring. Men vi kan också upptäcka att frågan, på grund av omkompilering, har flera planer med extremt olika minnesbidragsvärden som vi kan använda för att spåra problemet. Det finns en mängd olika sätt att undersöka problemet med hjälp av data som samlats in i Query Store, och det låter dig titta på problem både proaktivt och reaktivt.


  1. NLS_CHARSET_ID() Funktion i Oracle

  2. Infoga en array med Sequel gem i PostgreSQL

  3. Maximalt antal poster i en MySQL-databastabell

  4. Hur kan jag få tidig tillgång till Oracle Java-uppdateringar, så att jag kan testa min RIA och undvika brandövningar när dessa uppdateringar görs offentliga?