Deze vijfdelige serie duikt diep in de manier waarop parallelle plannen in rijmodus van SQL Server worden opgestart. Dit eerste deel behandelt de rol van de bovenliggende taak (coördinator) bij het voorbereiden van het plan voor parallelle uitvoering. Het omvat het initialiseren van elke operator en het toevoegen van verborgen profilers om runtime-prestatiegegevens te verzamelen, zoals het werkelijke aantal rijen en de verstreken tijd.
Setup
Om een concrete basis voor de analyse te bieden, zullen we volgen hoe een bepaalde parallelle query wordt uitgevoerd. Ik gebruikte de openbare Stack Overflow 2013 database (downloaddetails). De gewenste planvorm kan ook worden verkregen tegen de kleinere Stack Overflow 2010-gegevensset als dat handiger is. Het kan worden gedownload via dezelfde link. Ik heb één niet-geclusterde index toegevoegd:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
Mijn testomgeving is SQL Server 2019 CU9 op een laptop met 8 cores en 16GB geheugen toegewezen aan de instance. Compatibiliteitsniveau 150 wordt uitsluitend gebruikt. Ik vermeld die details om u te helpen het doelplan te reproduceren als u dat wenst. De basisprincipes van parallelle uitvoering in rijmodus zijn niet veranderd sinds SQL Server 2005, dus de volgende discussie is breed toepasbaar.
De testquery geeft het totale aantal vragen en antwoorden terug, gegroepeerd op maand en jaar:
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 );
Ik heb hints gebruikt om een rijmodusplan met een bepaalde vorm te krijgen. De uitvoering is beperkt tot DOP 2 om sommige details die later worden getoond beknopter te maken.
Het geschatte uitvoeringsplan is (klik om te vergroten):
Achtergrond
De query-optimizer produceert een enkel gecompileerd plan voor een batch. Elke verklaring in de batch is gemarkeerd voor seriële of parallelle uitvoering, afhankelijk van geschiktheid en geschatte kosten.
Een parallel plan bevat uitwisselingen (parallelisme-operators). Uitwisselingen kunnen verschijnen in streams distribueren , streams opnieuw verdelen , of streams verzamelen formulier. Elk van deze uitwisselingstypes gebruikt dezelfde onderliggende componenten, alleen anders bedraad, met een ander aantal in- en uitgangen. Voor meer achtergrondinformatie over parallelle uitvoering in rijmodus, zie Parallelle uitvoeringsplannen – takken en threads.
DOP-downgrade
De mate van parallellisme (DOP) voor een parallel plan kan indien nodig tijdens runtime worden verlaagd. Een parallelle query kan beginnen met het aanvragen van DOP 8, maar geleidelijk worden gedowngraded naar DOP 4, DOP 2 en uiteindelijk DOP 1 vanwege een gebrek aan systeembronnen op dat moment. Als je dat in actie wilt zien, bekijk dan deze korte video van Erik Darling.
Het uitvoeren van een parallel plan op een enkele thread kan ook gebeuren wanneer een parallel plan in de cache opnieuw wordt gebruikt door een sessie die beperkt is tot DOP 1 door een omgevingsinstelling (bijv. affiniteitsmasker of resource-gouverneur). Zie Mythe:SQL Server slaat een serieel abonnement op bij elk parallel abonnement voor meer informatie.
Wat de oorzaak ook is, DOP-downgrade van een parallel plan in de cache niet resulteren in het opstellen van een nieuw serieplan. SQL Server hergebruikt het bestaande parallelle plan door uit te schakelen de uitwisselingen. Het resultaat is een ‘parallel’ plan dat op één enkele thread wordt uitgevoerd. De uitwisselingen verschijnen nog steeds in het plan, maar worden tijdens runtime omzeild.
SQL Server kan een serieel plan niet promoveren naar parallelle uitvoering door uitwisselingen tijdens runtime toe te voegen. Dat zou een nieuwe compilatie vereisen.
Parallel-plan initialisatie
Een parallel plan heeft alle uitwisselingen die nodig zijn om extra werkthreads te gebruiken, maar er is extra instellingswerk nodig tijdens runtime voordat parallelle uitvoering kan beginnen. Een voor de hand liggend voorbeeld is dat extra werkthreads moeten worden toegewezen aan specifieke taken binnen het plan, maar er komt veel meer bij kijken.
Ik ga beginnen op het punt waar een parallel plan is opgehaald uit de plancache. Op dit moment bestaat alleen de oorspronkelijke thread die het huidige verzoek verwerkt. Deze thread wordt soms de "coördinatorthread" genoemd in parallelle plannen, maar ik geef de voorkeur aan de termen "oudertaak ’ of ‘moederwerker’. Er is verder niets bijzonders aan deze draad; het is dezelfde thread die de verbinding gebruikt om clientverzoeken te verwerken en seriële plannen uit te voeren tot ze zijn voltooid.
Om het punt te benadrukken dat slechts één enkele thread bestaat op dit moment wil ik dat je het plan op dit moment als volgt visualiseert:
Ik zal bijna uitsluitend screenshots van Sentry One Plan Explorer gebruiken in dit bericht, maar alleen voor deze eerste weergave zal ik ook de SSMS-versie laten zien:
In beide weergaven is het belangrijkste verschil het ontbreken van parallellismepictogrammen op elke operator, ook al zijn de uitwisselingen nog steeds aanwezig. Alleen de bovenliggende taak bestaat op dit moment en wordt uitgevoerd op de oorspronkelijke verbindingsthread. Geen extra werkthreads zijn al gereserveerd, gemaakt of toegewezen aan taken. Houd de bovenstaande planweergave voor ogen terwijl we doorgaan.
Het uitvoerbare plan maken
Het plan is op dit moment in wezen slechts een sjabloon dat kan worden gebruikt als basis voor eventuele toekomstige uitvoering. Om het klaar te maken voor een specifieke run, moet SQL Server runtime-waarden invullen, zoals de huidige gebruiker, transactiecontext, parameterwaarden, id's voor alle objecten die tijdens runtime zijn gemaakt (bijvoorbeeld tijdelijke tabellen en variabelen), enzovoort.
Voor een parallel plan moet SQL Server nogal wat extra voorbereidend werk doen om de interne machinerie op het punt te krijgen waarop de uitvoering kan beginnen. De werkthread van de bovenliggende taak is verantwoordelijk voor het uitvoeren van bijna al dit werk (en zeker al het werk dat we in deel 1 zullen behandelen).
Het proces van het transformeren van de plansjabloon voor een specifieke run staat bekend als het maken van het uitvoerbare plan . Het is soms moeilijk om de terminologie recht te houden, omdat termen vaak overbelast zijn en verkeerd worden toegepast (zelfs door Microsoft), maar ik zal mijn best doen om zo consistent mogelijk te zijn.
Uitvoeringscontexten
Je kunt denken aan een uitvoeringscontext als een plansjabloon gevuld met alle specifieke runtime-informatie die een bepaalde thread nodig heeft. Het uitvoerbare plan voor een serie statement bestaat uit een enkele uitvoeringscontext, waarbij een enkele thread het hele plan uitvoert.
Een parallel uitvoerbaar plan bevat een verzameling uitvoeringscontexten :Eén voor de bovenliggende taak en één per thread in elke parallelle vertakking. Elke extra parallelle worker-thread voert zijn deel van het algemene plan uit binnen zijn eigen uitvoeringscontext. Een parallel plan met drie takken die op DOP 8 draaien, heeft bijvoorbeeld (1 + (3 * 8)) =25 uitvoeringscontexten. Seriële uitvoeringscontexten worden in de cache opgeslagen voor hergebruik, maar aanvullende parallelle uitvoeringscontexten niet.
De bovenliggende taak bestaat altijd vóór eventuele aanvullende parallelle taken, dus wordt deze toegewezen aan uitvoeringscontext nul . De uitvoeringscontexten die door parallelle werkers worden gebruikt, worden later gemaakt, nadat de bovenliggende context volledig is geïnitialiseerd. De aanvullende contexten zijn gekloond uit de bovenliggende context, vervolgens aangepast voor hun specifieke taak (dit wordt behandeld in deel 2).
Bij het opstarten van uitvoeringscontext nul komt een aantal activiteiten kijken. Het is onpraktisch om te proberen ze allemaal op te sommen, maar het zal nuttig zijn om enkele van de belangrijkste te behandelen die van toepassing zijn op onze testquery. Er zullen er nog steeds te veel zijn voor een enkele lijst, dus ik zal ze opsplitsen in (enigszins willekeurige) secties:
1. Initialisatie van bovenliggende context
Wanneer we de instructie indienen voor uitvoering, wordt de context van de bovenliggende taak (uitvoeringscontext nul) geïnitialiseerd met:
- Een verwijzing naar de basistransactie (expliciet, impliciet of automatisch vastgelegd). Parallelle werkers zullen subtransacties uitvoeren, maar ze vallen allemaal binnen de basistransactie.
- Een lijst met parameters statement en hun huidige waarden.
- Een primair geheugenobject (PMO) gebruikt om geheugentoekenningen en -toewijzingen te beheren.
- Een gekoppelde kaart van de operators (query nodes) in het uitvoerbare plan.
- Een fabriek voor elk gewenst groot object (blob) handvatten.
- Lock klassen om meerdere sloten bij te houden die gedurende een bepaalde periode worden vastgehouden tijdens de uitvoering. Niet alle abonnementen vereisen vergrendelklassen aangezien operators met volledig streaming doorgaans afzonderlijke rijen in volgorde vergrendelen en ontgrendelen.
- De geschatte geheugentoelage voor de vraag.
- Geheugen in rijmodus verlenen feedback structuren voor elke operator (SQL Server 2019).
Veel van deze dingen zullen later worden gebruikt of waarnaar wordt verwezen door parallelle taken, dus ze moeten eerst in het bovenliggende bereik bestaan.
2. Metadata van bovenliggende context
De volgende hoofdtaken die worden uitgevoerd zijn:
- De geschatte zoekkosten controleren binnen de limiet ligt die is ingesteld door de configuratie-opties voor de kostenlimiet van de query gouverneur.
- indexgebruik bijwerken records – zichtbaar via
sys.dm_db_index_usage_stats
. - Cache-expressiewaarden maken (runtimeconstanten).
- Een lijst maken met profileringsoperators gebruikt om runtime-statistieken te verzamelen, zoals het aantal rijen en timings, als dit is aangevraagd voor de huidige uitvoering. De profilers zelf zijn nog niet gemaakt, alleen de lijst.
- Een momentopname maken van wachten voor de sessie-wachtfunctie beschikbaar via
sys.dm_exec_session_wait_stats
.
3. DOP en geheugentoekenning
De context van de bovenliggende taak nu:
- Berekent runtime mate van parallellisme (DOP ). Dit wordt beïnvloed door het aantal gratis werkers (zie "DOP-downgrade" eerder), waar ze tussen knooppunten kunnen worden geplaatst, en een aantal traceervlaggen.
- Reserveert het vereiste aantal threads. Deze stap is puur boekhouden - de threads zelf bestaan op dit moment mogelijk niet. SQL Server houdt het maximale aantal threads bij dat het mag hebben. Threads reserveren van dat getal aftrekt. Als de draden klaar zijn met, wordt het maximale aantal weer verhoogd.
- Stelt time-out voor geheugentoekenning in .
- Berekent de geheugentoekenning, inclusief geheugen dat nodig is voor uitwisselingsbuffers.
- Verkrijgt de geheugentoekenning via de juiste bronsemafoor .
- Maakt een managerobject om parallelle subprocessen af te handelen . De bovenliggende taak is het proces op het hoogste niveau; aanvullende taken zijn ook bekend als subprocessen .
Hoewel threads op dit moment 'gereserveerd' zijn, kan SQL Server nog steeds THREADPOOL
tegenkomen wacht later wanneer het een van de 'gereserveerde' threads probeert te gebruiken. De reservering garandeert dat SQL Server te allen tijde rond het geconfigureerde maximale aantal threads blijft, maar de fysieke thread is mogelijk niet onmiddellijk beschikbaar vanuit de thread-pool . Wanneer dat gebeurt, moet een nieuwe thread worden gestart door het besturingssysteem, wat even kan duren. Zie Unusual THREADPOOL Waits van Josh Darnell voor meer informatie.
4. Query scan instellen
Rijmodusplannen worden iteratief uitgevoerd mode, beginnend bij de wortel. Het plan dat we op dit moment hebben, is nog niet in staat tot deze manier van uitvoeren. Het is nog steeds grotendeels een sjabloon, ook al bevat het al een behoorlijke hoeveelheid uitvoeringsspecifieke informatie.
SQL Server moet de huidige structuur converteren naar een boomstructuur van iterators , elk met methoden zoals Open
, GetRow
, en Close
. De iteratormethoden zijn via functiewijzers met hun kinderen verbonden. Bijvoorbeeld door GetRow
. te bellen op de root roept recursief GetRow
. aan op onderliggende operators totdat een bladniveau is bereikt en een rij de boom begint te 'opborrelen'. Voor een opfriscursus over de details, zie iterators, queryplannen en waarom ze achteruit lopen.
Einde van deel 1
We hebben goede voortgang gemaakt met het opzetten van de uitvoeringscontext voor de bovenliggende taak. In deel 2 zullen we volgen terwijl SQL Server de query-scanstructuur construeert die nodig is voor iteratieve uitvoering.