I mit tidligere indlæg diskuterede jeg CXPACKET-venter og måder at forhindre eller begrænse parallelisme. Jeg forklarede også, hvordan kontroltråden i en parallel operation altid registrerer en CXPACKET-vent, og at nogle gange ikke-kontroltråde også kan registrere CXPACKET-venter. Dette kan ske, hvis en af trådene er blokeret og venter på en ressource (så alle de andre tråde slutter før den og registrerer CXPACKET venter også), eller hvis kardinalitetsestimater er forkerte. I dette indlæg vil jeg gerne udforske det sidste.
Når kardinalitetsestimaterne er forkerte, får de parallelle tråde, der udfører forespørgselsarbejdet, ujævne mængder arbejde at udføre. Det typiske tilfælde er, hvor en tråd får alt arbejdet, eller langt mere arbejde end de andre tråde. Det betyder, at de tråde, der afslutter behandlingen af deres rækker (hvis de overhovedet fik nogen), før den langsomste tråd, registrerer en CXPACKET fra det øjeblik, de slutter, til den langsomste tråd slutter. Dette problem kan føre til en tilsyneladende eksplosion i CXPACKET-venter og kaldes almindeligvis skæv parallelisme , fordi arbejdsfordelingen mellem de parallelle tråde er skæv, ikke ens.
Bemærk, at i SQL Server 2016 SP2 og SQL Server 2017 RTM CU3 registrerer forbrugertråde ikke længere CXPACKET-venter. De registrerer CXCONSUMER-venter, som er godartede og kan ignoreres. Dette er for at reducere antallet af CXPACKET-venter, der genereres, og de resterende er mere tilbøjelige til at kunne handles.
Eksempel på skæv parallelisme
Jeg vil gennemgå et konstrueret eksempel for at vise, hvordan man identificerer sådanne tilfælde.
Først og fremmest vil jeg oprette et scenario, hvor en tabel har vildt unøjagtige statistikker, ved manuelt at indstille antallet af rækker og sider i en OPDATERING STATISTIK erklæring (gør ikke dette 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å mit bord har kun et par tusinde rækker i sig, men jeg har forfalsket, at det har 10 millioner rækker.
Nu vil jeg oprette en konstrueret forespørgsel for at vælge de øverste 500 rækker, som vil gå parallelt, da den tror, der er millioner af rækker at scanne.
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
Og sæt det til at køre.
Visning af CXPACKET Waits
Nu kan jeg se på CXPACKET-venterne, der forekommer ved hjælp af et simpelt script til at se 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];
Hvis jeg udfører dette et par gange, ser jeg til sidst nogle resultater, der viser skæv parallelitet (jeg fjernede linket til forespørgselsplanens håndtag og indskrænkede ressourcebeskrivelsen for klarhedens skyld, og bemærk, at jeg satte koden i for at få fat i SQL-teksten, hvis du vil have det også):
session_id | exec_context_id | wait_duration_ms | wait_type | blocking_session_id | resource_description | 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 |
Resultater, der viser skæv parallelitet i aktion
Kontroltråden er den med exec_context_id sat til 0. De andre parallelle tråde er dem med exec_context_id højere end 0, og de viser alle CXPACKET-venter bortset fra én (bemærk at exec_context_id = 2
mangler på listen). Du vil bemærke, at de alle angiver deres eget session_id som den, der blokerer dem, og det er korrekt, fordi alle trådene venter på en anden tråd fra deres eget session_id at færdiggøre. database_id er databasen, i hvis kontekst forespørgslen udføres, ikke nødvendigvis den database, hvor problemet er, men det er det normalt, medmindre forespørgslen bruger tredelt navngivning til at udføre i en anden database.
Visning af kardinalitetsvurderingsproblemet
Med query_plan kolonne i forespørgselsoutputtet (som jeg fjernede for klarhedens skyld), kan du klikke på den for at få den grafiske plan frem og derefter højreklikke og vælge Vis med SQL Sentry Plan Explorer. Dette viser som nedenfor:
Jeg kan med det samme se, at der er et problem med kardinalitetsestimat, da de faktiske rækker for den grupperede indeksscanning kun er 2.048 sammenlignet med 10.000.000 estimerede (estimerede) rækker.
Hvis jeg ruller henover, kan jeg se fordelingen af rækker på tværs af de parallelle tråde, der blev brugt:
Se, kun en enkelt tråd udførte noget arbejde under den parallelle del af planen – den der ikke dukkede op i sys.dm_os_waiting_tasks output ovenfor.
I dette tilfælde er rettelsen at opdatere statistikkerne for tabellen.
I mit konstruerede eksempel virker det ikke, da der ikke er sket nogen ændringer i tabellen, så jeg kører opsætningsscriptet igen og udelader OPDATERING STATISTIK udmelding.
Forespørgselsplanen bliver så:
Hvor der ikke er noget kardinalitetsproblem og heller ingen parallelitet - problemet løst!
Oversigt
Hvis du ser CXPACKET-venter forekomme, er det nemt at tjekke for skæv parallelitet ved at bruge metoden beskrevet ovenfor. Alle de sager, jeg har set, har været på grund af problemer med kardinalitetsvurdering af den ene eller anden art, og ofte er det blot et tilfælde af opdatering af statistik.
Hvad angår generel ventestatistik, kan du finde mere information om brugen af dem til fejlfinding af ydeevne i:
- Min SQLskills blogindlægsserie, der starter med Vent-statistikker, eller fortæl mig venligst, hvor det gør ondt
- Mine ventetyper og låseklasser-bibliotek her
- Mit Pluralsight onlinekursus SQL Server:Fejlfinding af ydeevne ved hjælp af ventestatistik
- SQL Sentry
Indtil næste gang, god fejlfinding!