Denne artikel er den fjerde i en serie om optimeringstærskler. Serien dækker gruppering og aggregering af data, forklarer de forskellige algoritmer, som SQL Server kan bruge, og omkostningsmodellen, der hjælper den med at vælge mellem algoritmerne. I denne artikel fokuserer jeg på parallelitetsovervejelser. Jeg dækker de forskellige parallelitetsstrategier, som SQL Server kan bruge, tærsklerne for at vælge mellem en seriel og en parallel plan, og den omkostningslogik, som SQL Server anvender ved hjælp af et koncept kaldet grad af parallelisme for omkostningsberegning (DOP for kalkulation).
Jeg fortsætter med at bruge tabellen dbo.Orders i PerformanceV3-eksempeldatabasen i mine eksempler. Før du kører eksemplerne i denne artikel, skal du køre følgende kode for at slippe et par unødvendige indekser:
DROP INDEX HVIS FINDER idx_nc_sid_od_cid PÅ dbo.Orders; DROP INDEX HVIS EKSISTERER idx_unc_od_oid_i_cid_eid PÅ dbo.Orders;
De eneste to indekser, der skal være tilbage i denne tabel, er idx_cl_od (grupperet med ordredato som nøglen) og PK_Orders (ikke-klynget med ordre-id som nøglen).
Parallelismestrategier
Udover at skulle vælge mellem forskellige grupperings- og aggregeringsstrategier (forudbestilt Stream Aggregate, Sort + Stream Aggregate, Hash Aggregate), skal SQL Server også vælge, om den skal gå med en seriel eller en parallel plan. Faktisk kan den vælge mellem flere forskellige parallelitetsstrategier. SQL Server bruger omkostningslogik, der resulterer i optimeringstærskler, der under forskellige forhold gør, at én strategi foretrækkes frem for de andre. Vi har allerede diskuteret i dybden den omkostningslogik, som SQL Server bruger i serielle planer i de foregående dele af serien. I dette afsnit vil jeg introducere en række parallelitetsstrategier, som SQL Server kan bruge til at håndtere gruppering og aggregering. Indledningsvis vil jeg ikke komme ind på detaljerne i omkostningslogikken, men blot beskrive de tilgængelige muligheder. Senere i artiklen vil jeg forklare, hvordan kalkulationsformlerne fungerer, og en vigtig faktor i disse formler kaldet DOP for kalkulation.
Som du senere vil lære, tager SQL Server højde for antallet af logiske CPU'er i maskinen i sine omkostningsformler for parallelle planer. I mine eksempler, medmindre jeg siger andet, antager jeg, at målsystemet har 8 logiske CPU'er. Hvis du vil prøve de eksempler, jeg vil give, for at få de samme planer og omkostningsværdier som jeg gør, skal du også køre koden på en maskine med 8 logiske CPU'er. Hvis din maskine tilfældigvis har et andet antal CPU'er, kan du efterligne en maskine med 8 CPU'er - til omkostningsformål - som sådan:
DBCC OPTIMIZER_WHATIF(CPU'er, 8);
Selvom dette værktøj ikke er officielt dokumenteret og understøttet, er det ganske praktisk til forsknings- og læringsformål.
Tabellen Ordrer i vores eksempeldatabase har 1.000.000 rækker med ordre-id'er i intervallet 1 til 1.000.000. For at demonstrere tre forskellige parallelitetsstrategier til gruppering og aggregering, filtrerer jeg ordrer, hvor ordre-id'et er større end eller lig med 300.001 (700.000 matches), og grupperer dataene på tre forskellige måder (efter custid [20.000 grupper før filtrering], efter empid [500 grupper] og efter shipperid [5 grupper]), og udregn antallet af ordrer pr. gruppe.
Brug følgende kode til at oprette indekser til at understøtte de grupperede forespørgsler:
CREATE INDEX idx_oid_i_eid ON dbo.Orders(orderid) INCLUDE(empid);CREATE INDEX idx_oid_i_sid ON dbo.Orders(orderid) INCLUDE(shipperid);CREATE INDEX idx_oid_i_cid ON dbo.Orders);(orderid) INCLUDE(shipperid); før>Følgende forespørgsler implementerer den førnævnte filtrering og gruppering:
-- Forespørgsel 1:Serial SELECT custid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Forespørgsel 2:Parallel, ikke lokal/global SELECT custid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid; -- Forespørgsel 3:Lokal parallel global parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Forespørgsel 4:Lokal parallel global seriel SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;Bemærk, at forespørgsel 1 og forespørgsel 2 er de samme (begge grupper efter custid), kun førstnævnte fremtvinger en seriel plan, og sidstnævnte får en parallel plan på en maskine med 8 CPU'er. Jeg bruger disse to eksempler til at sammenligne de serielle og parallelle strategier for den samme forespørgsel.
Figur 1 viser de anslåede planer for alle fire forespørgsler:
Figur 1:Parallelismestrategier
For nu skal du ikke bekymre dig om omkostningsværdierne vist i figuren og omtalen af udtrykket DOP for omkostningsberegning. Jeg kommer til dem senere. Først skal du fokusere på at forstå strategierne og forskellene mellem dem.
Den strategi, der bruges i serieplanen for forespørgsel 1, burde være bekendt for dig fra de tidligere dele i serien. Planen filtrerer de relevante ordrer ved hjælp af en søgning i det dækkende indeks, du oprettede tidligere. Derefter, med det estimerede antal rækker, der skal grupperes og aggregeres, foretrækker optimeringsværktøjet Hash Aggregate-strategien frem for Sort + Stream Aggregate-strategien.
Planen for Query 2 bruger en simpel parallelismestrategi, der kun beskæftiger én samlet operatør. En parallel Index Seek-operator distribuerer pakker af rækker til de forskellige tråde på en round-robin måde. Hver pakke med rækker kan indeholde flere forskellige kunde-id'er. For at en enkelt aggregeret operator skal kunne beregne det korrekte endelige gruppeantal, skal alle rækker, der tilhører den samme gruppe, håndteres af den samme tråd. Af denne grund bruges en parallelisme (omfordelingsstrømme) udvekslingsoperatør til at opdele strømmene efter grupperingssættet (custid). Endelig bruges en parallelisme (Gather Streams) udvekslingsoperator til at samle strømmene fra de flere tråde i en enkelt strøm af resultatrækker.
Planerne for Query 3 og Query 4 anvender en mere kompleks parallelismestrategi. Planerne starter på samme måde som planen for Query 2, hvor en parallel Index Seek-operator distribuerer pakker med rækker til forskellige tråde. Derefter udføres aggregeringsarbejdet i to trin:en aggregeringsoperator grupperer og aggregerer lokalt rækkerne i den aktuelle tråd (bemærk partialagg1004-resultatmedlemmet), og en anden aggregeringsoperator grupperer og aggregerer globalt resultaterne af de lokale aggregater (bemærk globalagg1005). resultatmedlem). Hvert af de to aggregerede trin – lokale og globale – kan bruge enhver af de aggregerede algoritmer, jeg beskrev tidligere i serien. Begge planer for Query 3 og Query 4 starter med et lokalt Hash Aggregate og fortsætter med et globalt Sort + Stream Aggregate. Forskellen mellem de to er, at førstnævnte bruger parallelitet i begge trin (derfor bruges en Repartition Streams-udveksling mellem de to og en Gather Streams-udveksling efter det globale aggregat), og det senere håndterer det lokale aggregat i en parallel zone og den globale samles i en seriel zone (derfor bruges en Gather Streams-udveksling mellem de to).
Når du laver din research om forespørgselsoptimering generelt og parallelitet specifikt, er det godt at være bekendt med værktøjer, der gør dig i stand til at kontrollere forskellige optimeringsaspekter for at se deres effekter. Du ved allerede, hvordan du fremtvinger en seriel plan (med et MAXDOP 1-tip), og hvordan du emulerer et miljø, der til omkostningsformål har et vist antal logiske CPU'er (DBCC OPTIMIZER_WHATIF, med muligheden CPU'er). Et andet praktisk værktøj er forespørgselstippet ENABLE_PARALLEL_PLAN_PREFERENCE (introduceret i SQL Server 2016 SP1 CU2), som maksimerer parallelitet. Det jeg mener med dette er, at hvis en parallel plan understøttes for forespørgslen, vil parallelitet blive foretrukket i alle dele af planen, der kan håndteres parallelt, som om den var gratis. Bemærk for eksempel i figur 1, at planen for forespørgsel 4 som standard håndterer det lokale aggregat i en seriel zone og det globale aggregat i en parallel zone. Her er den samme forespørgsel, kun denne gang med ENABLE_PARALLEL_PLAN_PREFERENCE forespørgselstip anvendt (vi kalder det forespørgsel 5):
SELECT shipperid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperidOPTION(BRUG TIP('ENABLE_PARALLEL_PLAN_PREFERENCE'));Planen for forespørgsel 5 er vist i figur 2:
Figur 2:Maksimering af parallelisme
Bemærk, at både de lokale og globale aggregater denne gang håndteres i parallelle zoner.
Serial / parallel plan valg
Husk på, at under forespørgselsoptimering opretter SQL Server flere kandidatplaner og vælger den med den laveste pris blandt dem, den producerede. Udtrykket omkostning er lidt af en misvisende betegnelse, da kandidatplanen med de laveste omkostninger ifølge estimaterne formodes at være den med den laveste køretid, ikke den med den laveste mængde ressourcer, der bruges samlet set. For eksempel, mellem en seriel kandidatplan og en parallel, der er produceret til den samme forespørgsel, vil den parallelle plan sandsynligvis bruge flere ressourcer, da den skal bruge udvekslingsoperatører, der synkroniserer trådene (distribuere, ompartitionere og samle streams). For at den parallelle plan skal bruge mindre tid til at køre end den serielle plan, skal de besparelser, der opnås ved at udføre arbejdet med flere tråde, opveje det ekstra arbejde, som udvekslingsoperatørerne udfører. Og dette skal afspejles af de omkostningsformler, SQL Server bruger, når der er tale om parallelitet. Ikke en nem opgave at udføre præcist!
Ud over at omkostningerne ved en parallel plan skal være lavere end omkostningerne ved serieplanen for at blive foretrukket, skal omkostningerne ved serieplanalternativet være større end eller lig med omkostningstærsklen for parallelitet . Dette er en serverkonfigurationsindstilling, der er indstillet til 5 som standard, der forhindrer forespørgsler med ret lave omkostninger i at blive håndteret med parallelitet. Tankegangen her er, at et system med et stort antal små forespørgsler samlet set ville have større gavn af at bruge serielle planer, i stedet for at spilde en masse ressourcer på at synkronisere tråde. Du kan stadig have flere forespørgsler med serielle planer, der udføres på samme tid, og effektivt udnytte maskinens multi-CPU-ressourcer. Faktisk kan mange SQL Server-professionelle gerne øge omkostningstærsklen for parallelitet fra standardværdien på 5 til en højere værdi. Et system, der kører et temmelig lille antal store forespørgsler samtidigt, ville have meget mere gavn af at bruge parallelle planer.
For at opsummere, for at SQL Server kan foretrække en parallel plan frem for det serielle alternativ, skal serieplanomkostningerne mindst være omkostningstærsklen for parallelitet, og parallelplanomkostningerne skal være lavere end serieplanomkostningerne (hvilket potentielt indebærer lavere køretid).
Før jeg kommer til detaljerne i de faktiske omkostningsformler, vil jeg med eksempler illustrere forskellige scenarier, hvor der træffes et valg mellem serielle og parallelle planer. Sørg for, at dit system antager 8 logiske CPU'er for at få forespørgselsomkostninger svarende til mine, hvis du vil prøve eksemplerne.
Overvej følgende forespørgsler (vi kalder dem forespørgsel 6 og forespørgsel 7):
-- Forespørgsel 6:Serial SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid; -- Forespørgsel 7:Tvunget parallel SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empidOPTION(BRUG TIP('ENABLE_PARALLEL_PLAN_PREFERENCE'));Planerne for disse forespørgsler er vist i figur 3.
Figur 3:Serieomkostninger
Her er [tvungen] parallel planomkostning lavere end serieplanomkostningen; men prisen for serielplanen er lavere end standardomkostningstærsklen for parallelitet på 5, hvorfor SQL Server valgte serieplanen som standard.
Overvej følgende forespørgsler (vi kalder dem forespørgsel 8 og forespørgsel 9):
-- Forespørgsel 8:Parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Forespørgsel 9:Tvunget seriel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empidOPTION(MAXDOP 1);Planerne for disse forespørgsler er vist i figur 4.
Figur 4:Serieomkostninger>=omkostningstærskel for parallelitet, parallel omkostning
Her er de [tvungen] serieplanomkostninger større end eller lig med omkostningstærsklen for parallelitet, og parallelplanomkostningerne er lavere end serieplanomkostningerne, hvorfor SQL Server valgte parallelplanen som standard.
Overvej følgende forespørgsler (vi kalder dem forespørgsel 10 og forespørgsel 11):
-- Forespørgsel 10:Serial SELECT *FROM dbo.OrdersWHERE orderid>=100000; -- Forespørgsel 11:Tvunget parallel SELECT *FROM dbo.OrdersWHERE orderid>=100000OPTION(BRUG TIP('ENABLE_PARALLEL_PLAN_PREFERENCE'));Planerne for disse forespørgsler er vist i figur 5.
Figur 5:Serieomkostninger>=omkostningstærskel for parallelitet, parallel omkostning>=serieomkostninger
Her er serieplanomkostningerne større end eller lig med omkostningstærsklen for parallelitet; men omkostningerne til den serielle plan er lavere end den [tvungne] parallelle planomkostningen, hvorfor SQL Server valgte serieplanen som standard.
Der er en anden ting, du har brug for at vide om at forsøge at maksimere parallelitet med ENABLE_PARALLEL_PLAN_PREFERENCE tippet. For at SQL Server overhovedet skal være i stand til at bruge en parallel plan, skal der være en parallelitetsmuligator som et resterende prædikat, en sortering, et aggregat og så videre. En plan, der kun anvender en indeksscanning eller indekssøgning uden et resterende prædikat og uden nogen anden parallelitetsaktiverer, vil blive behandlet med en seriel plan. Betragt følgende forespørgsler som et eksempel (vi kalder dem forespørgsel 12 og forespørgsel 13):
-- Forespørgsel 12 SELECT *FROM dbo.OrdersOPTION(USE TIP('ENABLE_PARALLEL_PLAN_PREFERENCE')); -- Forespørgsel 13 VÆLG *FRA dbo.OrdersWHERE orderid>=100000OPTION(BRUG TIP('ENABLE_PARALLEL_PLAN_PREFERENCE'));Planerne for disse forespørgsler er vist i figur 6.
Figur 6:Parallelismeaktivering
Forespørgsel 12 får en seriel plan på trods af hint, da der ikke er nogen parallelitetsaktiverer. Forespørgsel 13 får en parallel plan, da der er et resterende prædikat involveret.
Beregning og test af DOP for omkostningsberegning
Microsoft var nødt til at kalibrere omkostningsformlerne i et forsøg på at have en lavere parallel planomkostninger end serieplanomkostningerne afspejler en lavere køretid og omvendt. En potentiel idé var at tage serieoperatørens CPU-omkostninger og simpelthen dividere den med antallet af logiske CPU'er i maskinen for at producere paralleloperatørens CPU-omkostninger. Det logiske antal CPU'er i maskinen er hovedfaktoren, der bestemmer forespørgslens grad af parallelitet, eller kort sagt DOP (antallet af tråde, der kan bruges i en parallel zone i planen). Den forenklede tankegang her er, at hvis en operatør tager T-tidsenheder at fuldføre, når man bruger en tråd, og forespørgslens grad af parallelitet er D, ville det tage operatørens T/D-tid at fuldføre, når man bruger D-tråde. I praksis er tingene ikke så enkle. For eksempel har du normalt flere forespørgsler, der kører samtidigt og ikke kun én, i hvilket tilfælde en enkelt forespørgsel ikke får alle maskinens CPU-ressourcer. Så Microsoft kom op med ideen om grad af parallelitet for omkostninger (DOP for omkostningsberegning, kort sagt). Dette mål er typisk lavere end antallet af logiske CPU'er i maskinen og er den faktor, som serieoperatørens CPU-omkostninger divideres med for at beregne paralleloperatørens CPU-omkostninger.
Normalt beregnes DOP for omkostningsberegning som antallet af logiske CPU'er divideret med 2 ved hjælp af heltalsdeling. Der er dog undtagelser. Når antallet af CPU'er er 2 eller 3, sættes DOP for omkostningsberegning til 2. Med 4 eller flere CPU'er sættes DOP til omkostningsberegning til #CPU'er / 2, igen ved hjælp af heltalsdivision. Det er op til et vist maksimum, som afhænger af mængden af tilgængelig hukommelse på maskinen. I en maskine med op til 4.096 MB hukommelse er den maksimale DOP til beregning 8; med mere end 4.096 MB, maksimalt DOP for omkostningsberegning er 32.
For at teste denne logik ved du allerede, hvordan du emulerer et ønsket antal logiske CPU'er ved hjælp af DBCC OPTIMIZER_WHATIF, med CPU'er-indstillingen, som sådan:
DBCC OPTIMIZER_WHATIF(CPU'er, 8);Ved at bruge den samme kommando med indstillingen MemoryMBs, kan du emulere en ønsket mængde hukommelse i MBs, som sådan:
DBCC OPTIMIZER_WHATIF(MemoryMBs, 16384);Brug følgende kode til at kontrollere den eksisterende status for de emulerede muligheder:
DBCC TRACEON(3604); DBCC OPTIMIZER_WHATIF(Status); DBCC TRACEOFF(3604);
Brug følgende kode til at nulstille alle muligheder:
DBCC OPTIMIZER_WHATIF(ResetAll);
Her er en T-SQL-forespørgsel, som du kan bruge til at beregne DOP til omkostning baseret på et input-antal af logiske CPU'er og mængden af hukommelse:
DECLARE @NumCPUs AS INT =8, @MemoryMBs AS INT =16384; SELECT CASE WHEN @NumCPUs =1 THEN 1 WHEN @NumCPUs <=3 THEN 2 WHEN @NumCPUs>=4 THEN (VÆLG MIN(n) FRA ( VALUES(@NumCPUs / 2), (MaxDOP4C ) ) AS D2(n)) END AS DOP4CFROM ( VALUES( CASE WHEN @MemoryMBs <=4096 THEN 8 ELSE 32 END ) ) AS D1(MaxDOP4C);
Med de angivne inputværdier returnerer denne forespørgsel 4.
Tabel 1 beskriver DOP for omkostninger, som du får baseret på det logiske antal CPU'er og mængden af hukommelse i din maskine.
#CPU'er | DOP til beregning, når MemoryMBs <=4096 | DOP til beregning, når MemoryMBs> 4096 |
---|---|---|
1 | 1 | 1 |
2-5 | 2 | 2 |
6-7 | 3 | 3 |
8-9 | 4 | 4 |
10-11 | 5 | 5 |
12-13 | 6 | 6 |
14-15 | 7 | 7 |
16-17 | 8 | 8 |
18-19 | 8 | 9 |
20-21 | 8 | 10 |
22-23 | 8 | 11 |
24-25 | 8 | 12 |
26-27 | 8 | 13 |
28-29 | 8 | 14 |
30-31 | 8 | 15 |
32-33 | 8 | 16 |
34-35 | 8 | 17 |
36-37 | 8 | 18 |
38-39 | 8 | 19 |
40-41 | 8 | 20 |
42-43 | 8 | 21 |
44-45 | 8 | 22 |
46-47 | 8 | 23 |
48-49 | 8 | 24 |
50-51 | 8 | 25 |
52-53 | 8 | 26 |
54-55 | 8 | 27 |
56-57 | 8 | 28 |
58-59 | 8 | 29 |
60-61 | 8 | 30 |
62-63 | 8 | 31 |
>=64 | 8 | 32 |
Tabel 1:DOP for omkostningsberegning
Lad os som et eksempel gense forespørgsel 1 og forespørgsel 2 vist tidligere:
-- Forespørgsel 1:Tvunget seriel SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Forespørgsel 2:Naturligvis parallel SELECT custid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid;
Planerne for disse forespørgsler er vist i figur 7.
Figur 7:DOP for koster
Forespørgsel 1 fremtvinger en seriel plan, hvorimod Query 2 får en parallel plan i mit miljø (emulerer 8 logiske CPU'er og 16.384 MB hukommelse). Det betyder, at DOP for omkostningsberegning i mit miljø er 4. Som nævnt beregnes en paralleloperatørs CPU-omkostning som serieoperatørens CPU-omkostning divideret med DOP til omkostningsberegning. Du kan se, at det faktisk er tilfældet i vores parallelle plan med operatørerne Index Seek og Hash Aggregate, som udføres parallelt.
Hvad angår omkostningerne for udvekslingsoperatørerne, er de lavet af en startomkostning og nogle konstante omkostninger pr. række, som du nemt kan omvendt udvikle.
Bemærk, at i den simple parallelle grupperings- og aggregeringsstrategi, som er den, der bruges her, er kardinalitetsestimaterne i serie- og parallelplanerne de samme. Det skyldes, at kun én samlet operatør er ansat. Senere vil du se, at tingene er anderledes, når du bruger den lokale/globale strategi.
Følgende forespørgsler hjælper med at illustrere effekten af antallet af logiske CPU'er og antallet af involverede rækker på forespørgselsomkostningerne (10 forespørgsler, med trin på 100.000 rækker):
SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=900001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=800001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=700001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=600001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=500001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=200001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=100001GROUP BY empid; VÆLG empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=000001GROUP BY empid;
Figur 8 viser resultaterne.
Figur 8:Forespørgselsomkostninger med hensyn til #CPU'er og #rækker
Den grønne linje repræsenterer omkostningerne ved de forskellige forespørgsler (med forskellige antal rækker) ved brug af en seriel plan. De andre linjer repræsenterer omkostningerne ved de parallelle planer med forskellige antal logiske CPU'er og deres respektive DOP til omkostningsberegning. Den røde linje repræsenterer det punkt, hvor den serielle forespørgselsomkostning er 5 - standardomkostningstærsklen for indstilling af parallelitet. Til venstre for dette punkt (færre rækker, der skal grupperes og aggregeres), vil optimeringsværktøjet normalt ikke overveje en parallel plan. For at kunne undersøge omkostningerne ved parallelle planer under omkostningstærsklen for parallelitet, kan du gøre en af to ting. En mulighed er at bruge forespørgselstippet ENABLE_PARALLEL_PLAN_PREFERENCE, men som en påmindelse maksimerer denne mulighed parallelisme i modsætning til blot at tvinge den. Hvis det ikke er den ønskede effekt, kan du bare deaktivere omkostningstærsklen for parallelitet, som sådan:
EXEC sp_configure 'vis avancerede indstillinger', 1; REKONFIGURER; EXEC sp_configure 'omkostningstærskel for parallelisme', 0; EXEC sp_configure 'vis avancerede muligheder', 0; GENKONFIGURER;
Det er naturligvis ikke et smart træk i et produktionssystem, men perfekt nyttigt til forskningsformål. Det var, hvad jeg gjorde for at producere informationen til diagrammet i figur 8.
Startende med 100K rækker og tilføjelse af 100K intervaller, synes alle grafer at antyde, at hvis omkostningstærsklen for parallelitet ikke var en faktor, ville en parallel plan altid have været foretrukket. Det er faktisk tilfældet med vores forespørgsler og antallet af involverede rækker. Prøv dog et mindre antal rækker, startende med 10.000 og øg med 10.000 trin ved hjælp af følgende fem forespørgsler (igen, hold omkostningstærsklen for parallelitet deaktiveret indtil videre):
SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=990001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=980001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=970001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=960001GROUP BY empid; SELECT empid, COUNT(*) AS nummordersFROM dbo.OrdersWHERE orderid>=950001GROUP BY empid;
Figur 9 viser forespørgselsomkostningerne med både serielle og parallelle planer (emulerer 4 CPU'er, DOP for koster 2).
Figur 9:Seriel / parallel plan tærskel
Som du kan se, er der en optimeringstærskel, op til hvilken serieplanen foretrækkes, og over hvilken den parallelle plan foretrækkes. Som nævnt, i et normalt system, hvor du enten holder omkostningstærsklen for parallelitetsindstilling på standardværdien på 5 eller højere, er den effektive tærskel alligevel højere end i denne graf.
Tidligere nævnte jeg, at når SQL Server vælger den simple grupperings- og aggregerings-parallelismestrategi, er kardinalitetsestimaterne for de serielle og parallelle planer de samme. Spørgsmålet er, hvordan SQL Server håndterer kardinalitetsestimaterne for den lokale/globale parallelismestrategi.
For at finde ud af dette, bruger jeg Query 3 og Query 4 fra vores tidligere eksempler:
-- Forespørgsel 3:Lokal parallel global parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Forespørgsel 4:Lokal parallel global seriel SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;
I et system med 8 logiske CPU'er og en effektiv DOP til omkostningsværdi på 4, fik jeg planerne vist i figur 10.
Figur 10:Kardinalitetsestimat
Forespørgsel 3 grupperer ordrerne efter empid. Der forventes efterhånden 500 forskellige medarbejdergrupper.
Forespørgsel 4 grupperer ordrerne efter afsenderid. 5 forskellige afsendergrupper forventes i sidste ende.
Mærkeligt nok ser det ud til, at kardinalitetsestimatet for antallet af grupper produceret af det lokale aggregat er { antal distinkte grupper forventet af hver tråd } * { DOP for omkostningsberegning }. I praksis indser du, at antallet normalt vil være dobbelt så meget, da det, der tæller, er DOP'en for udførelse (aka, bare DOP), som primært er baseret på antallet af logiske CPU'er. Denne del er en smule vanskelig at efterligne til forskningsformål, da DBCC OPTIMIZER_WHATIF-kommandoen med CPU'er-indstillingen påvirker beregningen af DOP til omkostningsberegning, men DOP til udførelse vil ikke være større end det faktiske antal logiske CPU'er, som din SQL Server-instans ser. Dette tal er i det væsentlige baseret på antallet af planlæggere, som SQL Server starter med. Du kan kontroller antallet af planlæggere, SQL Server starter med ved hjælp af -P{ #schedulers } opstartsparameteren, men det er et lidt mere aggressivt forskningsværktøj sammenlignet med en sessionsindstilling.
I hvert fald, uden at emulere nogen ressourcer, har min testmaskine 4 logiske CPU'er, hvilket resulterer i DOP for omkostning 2 og DOP for udførelse 4. I mit miljø viser det lokale aggregat i planen for Query 3 et estimat på 1.000 resultatgrupper (500 x 2) og en faktisk på 2.000 (500 x 4). På samme måde viser det lokale aggregat i planen for forespørgsel 4 et estimat på 10 resultatgrupper (5 x 2) og en faktisk på 20 (5 x 4).
Når du er færdig med at eksperimentere, skal du køre følgende kode til oprydning:
-- Indstil omkostningstærskel for parallelitet til standard EXEC sp_configure 'vis avancerede muligheder', 1; REKONFIGURER; EXEC sp_configure 'omkostningstærskel for parallelisme', 5; EXEC sp_configure 'vis avancerede muligheder', 0; RECONFIGURE;GO -- Nulstil OPTIMIZER_WHATIF muligheder DBCC OPTIMIZER_WHATIF(ResetAll); -- Drop indekser DROP INDEX idx_oid_i_sid ON dbo.Orders;DROP INDEX idx_oid_i_eid ON dbo.Orders;DROP INDEX idx_oid_i_cid ON dbo.Orders;
Konklusion
I denne artikel beskrev jeg en række parallelismestrategier, som SQL Server bruger til at håndtere gruppering og aggregering. Et vigtigt koncept at forstå i optimering af forespørgsler med parallelle planer er graden af parallelisme (DOP) for omkostningsberegning. Jeg viste en række optimeringstærskler, herunder en tærskel mellem serielle og parallelle planer og indstilling af omkostningstærskel for parallelitet. De fleste af de begreber, jeg beskrev her, er ikke unikke for gruppering og aggregering, men er snarere lige så anvendelige for parallelle planovervejelser i SQL Server generelt. Næste måned fortsætter jeg serien ved at diskutere optimering med forespørgselsomskrivninger.