I mitt tidigare inlägg diskuterade jag CXPACKET-väntningar och sätt att förhindra eller begränsa parallellism. Jag förklarade också hur kontrolltråden i en parallelloperation alltid registrerar en CXPACKET-väntning, och att ibland icke-kontrolltrådar också kan registrera CXPACKET-väntningar. Detta kan hända om en av trådarna är blockerad i väntan på en resurs (så att alla andra trådar avslutas innan den och registrerar CXPACKET väntar också), eller om kardinalitetsuppskattningar är felaktiga. I det här inlägget vill jag utforska det senare.
När kardinalitetsuppskattningar är felaktiga får de parallella trådarna som utför frågearbetet ojämna mängder arbete att göra. Det typiska fallet är när en tråd får allt arbete, eller mycket mer arbete än de andra trådarna. Det betyder att de trådar som avslutar bearbetningen av sina rader (om de ens fick några) innan den långsammaste tråden registrerar en CXPACKET från det ögonblick de slutar tills den långsammaste tråden slutar. Detta problem kan leda till att en till synes explosion i CXPACKET-väntningar inträffar och kallas vanligtvis skev parallellism , eftersom arbetsfördelningen mellan de parallella trådarna är skev, inte ens.
Observera att i SQL Server 2016 SP2 och SQL Server 2017 RTM CU3 registrerar inte längre konsumenttrådar CXPACKET-väntningar. De registrerar CXCONSUMER-väntningar, som är godartade och kan ignoreras. Detta för att minska antalet CXPACKET-väntningar som genereras, och de återstående är mer sannolikt att de kan åtgärdas.
Exempel på skev parallellism
Jag ska gå igenom ett konstruerat exempel för att visa hur man identifierar sådana fall.
Först och främst skapar jag ett scenario där en tabell har väldigt felaktig statistik, genom att manuellt ställa in antalet rader och sidor i en UPPDATERA STATISTIK uttalande (gör inte detta i produktionen!):
USE [master]; GO IF DB_ID (N'ExecutionMemory') IS NOT NULL BEGIN ALTER DATABASE [ExecutionMemory] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [ExecutionMemory]; END GO CREATE DATABASE [ExecutionMemory]; GO USE [ExecutionMemory]; GO CREATE TABLE dbo.[Test] ( [RowID] INT IDENTITY, [ParentID] INT, [CurrentValue] NVARCHAR (100), CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([RowID])); GO INSERT INTO dbo.[Test] ([ParentID], [CurrentValue]) SELECT CASE WHEN ([t1].[number] % 3 = 0) THEN [t1].[number] – [t1].[number] % 6 ELSE [t1].[number] END, 'Test' + CAST ([t1].[number] % 2 AS VARCHAR(11)) FROM [master].[dbo].[spt_values] AS [t1] WHERE [t1].[type] = 'P'; GO UPDATE STATISTICS dbo.[Test] ([PK_Test]) WITH ROWCOUNT = 10000000, PAGECOUNT = 1000000; GO
Så mitt bord har bara några tusen rader i sig, men jag har fejkat att det har 10 miljoner rader.
Nu ska jag skapa en konstruerad fråga för att välja de 500 översta raderna, som kommer att gå parallellt eftersom den tror att det finns miljontals rader att skanna.
USE [ExecutionMemory]; GO SET NOCOUNT ON; GO DECLARE @CurrentValue NVARCHAR (100); WHILE (1=1) SELECT TOP (500) @CurrentValue = [CurrentValue] FROM dbo.[Test] ORDER BY NEWID() DESC; GO
Och sätt igång det.
Visa CXPACKET Waits
Nu kan jag titta på CXPACKET-väntningarna som inträffar med ett enkelt skript för att titta på sys.dm_os_waiting_tasks DMV:
SELECT [owt].[session_id], [owt].[exec_context_id], [owt].[wait_duration_ms], [owt].[wait_type], [owt].[blocking_session_id], [owt].[resource_description], [er].[database_id], [eqp].[query_plan] FROM sys.dm_os_waiting_tasks [owt] INNER JOIN sys.dm_exec_sessions [es] ON [owt].[session_id] = [es].[session_id] INNER JOIN sys.dm_exec_requests [er] ON [es].[session_id] = [er].[session_id] OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est] OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp] WHERE [es].[is_user_process] = 1 ORDER BY [owt].[session_id], [owt].[exec_context_id];
Om jag kör detta några gånger ser jag så småningom några resultat som visar skev parallellitet (jag tog bort länken för frågeplanens handtag och förkortade resursbeskrivningen, för tydlighetens skull, och märk att jag lägger in koden för att ta tag i SQL-texten om du vill ha det också):
session_id | exec_context_id | wait_duration_ms | wait_type | blocking_session_id | resursbeskrivning | database_id |
---|---|---|---|---|---|---|
56 | 0 | 1 | CXPACKET | NULL | exchangeEvent | 13 |
56 | 1 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 3 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 4 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 5 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 6 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 7 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
Resultat som visar skev parallellism i aktion
Kontrolltråden är den med exec_context_id satt till 0. De andra parallella trådarna är de med exec_context_id högre än 0, och de visar alla CXPACKET-väntningar förutom en (observera att exec_context_id = 2
saknas i listan). Du kommer att märka att de alla listar sitt eget session_id som den som blockerar dem, och det är korrekt eftersom alla trådar väntar på en annan tråd från sitt eget session_id att slutföra. databasens_id är databasen i vars sammanhang frågan exekveras, inte nödvändigtvis databasen där problemet finns, men det är det vanligtvis om inte frågan använder tredelad namngivning för att köras i en annan databas.
Visa problemet med uppskattning av kardinalitet
Med query_plan kolumnen i frågeutgången (som jag tog bort för tydlighetens skull), kan du klicka på den för att ta fram den grafiska planen och sedan högerklicka och välja Visa med SQL Sentry Plan Explorer. Detta visas som nedan:
Jag kan omedelbart se att det finns ett problem med uppskattning av kardinalitet, eftersom de faktiska raderna för den klustrade indexskanningen bara är 2 048, jämfört med 10 000 000 uppskattade (uppskattade) rader.
Om jag rullar över kan jag se fördelningen av rader över de parallella trådarna som användes:
Se och se, bara en enda tråd gjorde något arbete under den parallella delen av planen – den som inte dök upp i sys.dm_os_waiting_tasks utgång ovan.
I det här fallet är korrigeringen att uppdatera statistiken för tabellen.
I mitt konstruerade exempel kommer det inte att fungera, eftersom det inte har gjorts några ändringar i tabellen, så jag kör om konfigurationsskriptet och utelämnar UPPDATERA STATISTIK påstående.
Frågeplanen blir då:
Där det inte finns något kardinalitetsproblem och ingen parallellitet heller – problemet löst!
Sammanfattning
Om du ser att CXPACKET-väntningar inträffar är det lätt att kontrollera om det finns skev parallellitet med metoden som beskrivs ovan. Alla fall jag har sett har berott på problem med kardinalitetsuppskattning av ett eller annat slag, och ofta handlar det helt enkelt om att uppdatera statistik.
När det gäller allmän väntestatistik kan du hitta mer information om hur du använder dem för prestandafelsökning i:
- Min SQLskills blogginläggsserie, som börjar med Vänta statistik, eller snälla berätta för mig var det gör ont
- Mina väntetyper och låsklasser-bibliotek här
- Min Pluralsight onlinekurs SQL Server:Prestandafelsökning med hjälp av väntastatistik
- SQL Sentry
Tills nästa gång, lycklig felsökning!