Denna femdelade serie tar en djupdykning i hur parallella planer för SQL Server-radläge startar. Denna första del tar upp rollen som föräldrauppgiften (koordinatorn) vid utarbetandet av planen för parallellt genomförande. Det inkluderar att initiera varje operatör och lägga till dolda profiler för att samla in körtidsprestandadata som faktisk radantal och förfluten tid.
Inställningar
För att ge ett konkret underlag för analysen kommer vi att följa hur en viss parallellfråga startar exekvering. Jag använde den offentliga Stack Overflow 2013 databas (nedladdningsinformation). Den önskade planformen kan också erhållas mot den mindre Stack Overflow 2010-datauppsättningen om det är bekvämare. Den kan laddas ner på samma länk. Jag lade till ett icke-klustrat index:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
Min testmiljö är SQL Server 2019 CU9 på en bärbar dator med 8 kärnor och 16 GB minne tilldelat instansen. Kompatibilitetsnivå 150 används uteslutande. Jag nämner dessa detaljer för att hjälpa dig att reproducera målplanen om du vill. Grunderna för parallellkörning av radläge har inte förändrats sedan SQL Server 2005, så följande diskussion är allmänt tillämplig.
Testfrågan returnerar det totala antalet frågor och svar, grupperade efter månad och år:
WITH MonthlyPosts AS ( SELECT P.PostTypeId, CA.TheYear, CA.TheMonth, Latest = MAX(P.CreationDate) FROM dbo.Posts AS P CROSS APPLY ( VALUES ( YEAR(P.CreationDate), MONTH(P.CreationDate) ) ) AS CA (TheYear, TheMonth) GROUP BY P.PostTypeId, CA.TheYear, CA.TheMonth ) SELECT rn = ROW_NUMBER() OVER ( ORDER BY Q.TheYear, Q.TheMonth), Q.TheYear, Q.TheMonth, LatestQuestion = Q.Latest, LatestAnswer = A.Latest FROM MonthlyPosts AS Q JOIN MonthlyPosts AS A ON A.TheYear = Q.TheYear AND A.TheMonth = Q.TheMonth WHERE Q.PostTypeId = 1 AND A.PostTypeId = 2 ORDER BY Q.TheYear, Q.TheMonth OPTION ( USE HINT ('DISALLOW_BATCH_MODE'), USE HINT ('FORCE_DEFAULT_CARDINALITY_ESTIMATION'), ORDER GROUP, MAXDOP 2 );
Jag har använt tips för att få en plan för en viss formradsläge. Exekveringen är begränsad till DOP 2 för att göra några av detaljerna som visas senare mer kortfattade.
Den beräknade genomförandeplanen är (klicka för att förstora):
Bakgrund
Frågeoptimeraren producerar en enda kompilerad plan för en batch. Varje uttalande i partiet är markerat för seriell eller parallell exekvering, beroende på behörighet och beräknade kostnader.
En parallell plan innehåller utbyten (parallellismoperatorer). Utbyten kan visas i distributionsströmmar , ompartitionsströmmar , eller samla strömmar form. Var och en av dessa utbytestyper använder samma underliggande komponenter, bara kopplade på olika sätt, med olika antal ingångar och utgångar. För mer bakgrund om radläge parallellt exekvering, se Parallella exekveringsplaner – grenar och trådar.
DOP-nedgradering
Graden av parallellism (DOP) för en parallell plan kan vid behov nedgraderas vid körning. En parallell fråga kan börja med att begära DOP 8, men gradvis nedgraderas till DOP 4, DOP 2 och slutligen DOP 1 på grund av brist på systemresurser i det ögonblicket. Om du vill se det i aktion, se den här korta videon av Erik Darling.
Att köra en parallell plan på en enskild tråd kan också hända när en cachad parallellplan återanvänds av en session som är begränsad till DOP 1 av en miljöinställning (t.ex. affinitetsmask eller resursregulator). Se Myt:SQL Server cachar en serieplan med varje parallellplan för detaljer.
Oavsett orsak, inte gör DOP-nedgradering av en cachad parallell plan resultera i att en ny serieplan sammanställs. SQL Server återanvänder den befintliga parallella planen genom att inaktivera utbytena. Resultatet är en "parallell" plan som körs på en enda tråd. Utbytena visas fortfarande i planen, men de förbigås vid körning.
SQL Server kan inte främja en seriell plan till parallell exekvering genom att lägga till utbyten vid körning. Det skulle kräva en ny sammanställning.
Parallell planinitiering
En parallell plan har alla utbyten som behövs för att använda extra arbetstrådar, men det finns ytterligare installationsarbete behövs under körning innan parallell exekvering kan påbörjas. Ett uppenbart exempel är att ytterligare arbetstrådar måste allokeras till specifika uppgifter inom planen, men det finns mycket mer i det än så.
Jag ska börja vid den punkt där en parallell plan har hämtats från plancachen. Vid denna tidpunkt finns bara den ursprungliga tråden som behandlar den aktuella begäran. Den här tråden kallas ibland för "koordinatortråden" i parallella planer, men jag föredrar termerna "förälderuppgift ” eller ”föräldraarbetare”. Det är annars inget speciellt med den här tråden; det är samma tråd som anslutningen använder för att bearbeta klientförfrågningar och köra serieplaner till slut.
För att understryka poängen att bara en enda tråd existerar just nu vill jag att du ska visualisera planen vid denna tidpunkt så här:
Jag kommer att använda skärmdumpar från Sentry One Plan Explorer nästan uteslutande i det här inlägget, men endast för denna första visning kommer jag också att visa SSMS-versionen:
I båda representationerna är den viktigaste skillnaden avsaknaden av parallellitetsikoner på varje operatör, även om utbytena fortfarande är närvarande. Bara den överordnade uppgiften existerar just nu och körs på den ursprungliga anslutningstråden. Inga ytterligare arbetstrådar har reserverats, skapats eller tilldelats uppgifter ännu. Ha ovanstående planrepresentation framför sinnet när vi fortsätter.
Skapa den körbara planen
Planen vid denna tidpunkt är i huvudsak bara en mall som kan användas som grund för eventuella framtida utförande. För att göra den redo för en specifik körning måste SQL Server fylla i körtidsvärden som den aktuella användaren, transaktionskontext, parametervärden, ID för alla objekt som skapas vid körning (t.ex. temporära tabeller och variabler) och så vidare.
För en parallell plan behöver SQL Server göra en hel del ytterligare förberedande arbete för att få det interna maskineriet till den punkt där exekvering kan börja. Den överordnade uppgiftens arbetstråd är ansvarig för att utföra nästan allt detta arbete (och säkerligen allt arbete som vi kommer att täcka i del 1).
Processen att transformera planmallen för en specifik körning kallas att skapa den körbara planen . Det är ibland svårt att hålla terminologin rak, eftersom termer ofta överbelastas och används felaktigt (även av Microsoft), men jag ska göra mitt bästa för att vara så konsekvent som möjligt.
Körningssammanhang
Du kan tänka på en exekveringskontext som en planmall fylld med all specifik körtidsinformation som behövs av en viss tråd. Den körbara planen för en serie satsen består av en enda exekveringskontext, där en enda tråd kör hela planen.
En parallell den körbara planen innehåller en samling exekveringskontexter :En för den överordnade uppgiften och en per tråd i varje parallellgren. Varje ytterligare parallell arbetstråd driver sin del av den övergripande planen inom sitt eget exekveringssammanhang. Till exempel har en parallell plan med tre grenar som körs vid DOP 8 (1 + (3 * 8)) =25 exekveringskontexter. Seriella körningskontexter cachelagras för återanvändning, men ytterligare parallella körningskontexter är det inte.
Den överordnade uppgiften finns alltid före eventuella ytterligare parallella uppgifter, så den tilldelas exekveringskontext noll . Exekveringskontexterna som används av parallella arbetare kommer att skapas senare, efter att den överordnade kontexten är helt initialiserad. De ytterligare sammanhangen är klonade från det överordnade sammanhanget, sedan anpassade för deras specifika uppgift (detta tas upp i del 2).
Det finns ett antal aktiviteter involverade i att starta upp exekveringskontext noll. Det är opraktiskt att försöka lista dem alla, men det kommer att vara användbart att täcka några av de viktigaste som är tillämpliga på vår testfråga. Det kommer fortfarande att finnas för många för en enda lista, så jag delar upp dem i (något godtyckliga) avsnitt:
1. Initiering av föräldrakontext
När vi skickar in satsen för exekvering initieras den överordnade uppgiftens kontext (exekveringskontext noll) med:
- En referens till bastransaktionen (explicit, implicit eller auto-commit). Parallella arbetare kommer att köra deltransaktioner, men de omfattas alla av bastransaktionen.
- En lista med parametrar på satsen och deras nuvarande värden.
- Ett primärt minnesobjekt (PMO) som används för att hantera minnesanslag och tilldelningar.
- En länkad karta av operatörerna (frågenoder) i den körbara planen.
- En fabrik för alla nödvändiga stora objekt (blob) handtag.
- Lås klasser för att hålla reda på flera lås som hålls under en period under körning. Alla planer kräver inte låsklasser eftersom heltströmmande operatörer vanligtvis låser och låser upp enskilda rader i följd.
- Det beräknade minnesbidraget för frågan.
- Bevilja feedback i radlägesminne strukturer för varje operatör (SQL Server 2019).
Många av dessa saker kommer att användas eller hänvisas till av parallella uppgifter senare, så de måste finnas i överordnad omfattning först.
2. Metadata för överordnad kontext
Nästa huvuduppgifter som utförs är:
- Kontrollerar den beräknade frågekostnaden är inom den gräns som anges av konfigurationsalternativen för kostnadsgräns för frågeregulatorn.
- Uppdaterar indexanvändning poster – exponerade genom
sys.dm_db_index_usage_stats
. - Skapa cachade uttrycksvärden (körtidskonstanter).
- Skapa en lista över profileringsoperatörer används för att samla in runtime-mått som radantal och timings, om detta har begärts för den aktuella körningen. Profilerna själva är inte skapade ännu, bara listan.
- Ta en ögonblicksbild av väntningar för session waits-funktionen exponerad via
sys.dm_exec_session_wait_stats
.
3. DOP och minnesbidrag
Kontexten för överordnad uppgift nu:
- Beräknar runtime grad av parallellism (DOP ). Detta påverkas av antalet lediga arbetare (se "DOP-nedgradering" tidigare), där de kan placeras bland noder och ett antal spårningsflaggor.
- Reserverar erforderligt antal trådar. Det här steget är ren redovisning – själva trådarna kanske inte existerar vid denna tidpunkt. SQL Server håller reda på det maximala antalet trådar den är tillåten att ha. Reservera trådar subtraherar från det talet. När trådarna är klara ökas maxantalet igen.
- Ställer in minnesbeviljande timeout .
- Beräknar minnestillståndet, inklusive minne som krävs för utbytesbuffertar.
- Erhåller minnesanslaget via lämplig resurssemafor .
- Skapar ett förvaltarobjekt för att hantera parallella underprocesser . Förälderuppgiften är processen på högsta nivå; ytterligare uppgifter är också kända som underprocesser .
Även om trådar är "reserverade" vid det här laget, kan SQL Server fortfarande stöta på THREADPOOL
väntar senare när den försöker använda en av de "reserverade" trådarna. Reservationen garanterar att SQL Server kommer att finnas kvar runt sitt konfigurerade maximala antal trådar hela tiden, men den fysiska tråden kanske inte är omedelbart tillgänglig från trådpoolen . När det händer kommer en ny tråd att behöva startas av operativsystemet, vilket kan ta en liten stund. För mer om det, se Unusual THREADPOOL Waits av Josh Darnell.
4. Inställning av sökfråga
Radlägesplaner körs i iterativ mode, med början vid roten. Planen vi har för tillfället är ännu inte kapabel att genomföra detta sätt. Det är fortfarande till stor del en mall, även om den redan innehåller en hel del exekveringsspecifik information.
SQL Server behöver konvertera den nuvarande strukturen till ett träd med iteratorer , var och en med metoder som Open
, GetRow
och Close
. Iteratormetoderna är kopplade till sina barn via funktionspekare. Till exempel, anropa GetRow
på roten anropar GetRow
rekursivt på barnoperatörer tills en lövnivå nås och en rad börjar "bubbla upp" trädet. För en uppdatering av detaljerna se Iteratorer, frågeplaner och varför de körs baklänges.
Slutet av del 1
Vi har gjort goda framsteg med att sätta upp exekveringskontexten för den överordnade uppgiften. I del 2 kommer vi att följa med när SQL Server konstruerar frågesökningsträdet som krävs för iterativ exekvering.