For nylig blev jeg introduceret til en ny ø-udfordring af min ven Erland Sommarskog. Det er baseret på et spørgsmål fra et offentligt databaseforum. Selve udfordringen er ikke kompliceret at håndtere, når man bruger velkendte teknikker, som primært anvender vinduesfunktioner. Disse teknikker kræver dog eksplicit sortering på trods af tilstedeværelsen af et understøttende indeks. Dette påvirker løsningernes skalerbarhed og responstid. Glad for udfordringer satte jeg mig for at finde en løsning for at minimere antallet af eksplicitte sorteringsoperatører i planen, eller endnu bedre, eliminere behovet for dem helt. Og jeg fandt sådan en løsning.
Jeg vil starte med at præsentere en generaliseret form for udfordringen. Jeg viser derefter to løsninger baseret på kendte teknikker, efterfulgt af den nye løsning. Til sidst vil jeg sammenligne ydeevnen af de forskellige løsninger.
Jeg anbefaler, at du prøver at finde en løsning, før du implementerer min.
Udfordringen
Jeg vil præsentere en generaliseret form for Erlands oprindelige ø-udfordring.
Brug følgende kode til at oprette en tabel kaldet T1 og udfylde den med et lille sæt eksempeldata:
INDSTIL ANTAL TIL; BRUG tempdb; DROP TABLE HVIS FINDER dbo.T1 OPRET TABEL dbo.T1( grp VARCHAR(10) NOT NULL, ord INT NOT NULL, val VARCHAR(10) NOT NULL, CONSTRAINT PK_T1 PRIMARY KEY(grp, ord));GO INSERT INTO dbo. T1(grp, ord, val) VALUES ('Gruppe A', 1002, 'Y'), ('Gruppe A', 1003, 'Y'), ('Gruppe A', 1005, 'Y'), (' Gruppe A', 1007, 'N'), ('Gruppe A', 1011, 'N'), ('Gruppe A', 1013, 'N'), ('Gruppe A', 1017, 'Y'), ('Gruppe A', 1019, 'Y'), ('Gruppe A', 1023, 'N'), ('Gruppe A', 1029, 'N'), ('Gruppe B', 1001, 'X' ), ('Gruppe B', 1002, 'X'), ('Gruppe B', 1003, 'Z'), ('Gruppe B', 1005, 'Z'), ('Gruppe B', 1008, ' Z'), ('Gruppe B', 1013, 'Z'), ('Gruppe B', 1021, 'Y'), ('Gruppe B', 1034, 'Y');
Udfordringen er som følger:
Forudsat opdeling baseret på kolonnen grp og rækkefølge baseret på kolonnen ord, beregne sekventielle rækkenumre, der starter med 1 inden for hver fortløbende gruppe af rækker med samme værdi i valkolonnen. Følgende er det ønskede resultat for det givne lille sæt eksempeldata:
grp ord val seqno-------- ----- ---- ------Gruppe A 1002 Y 1Gruppe A 1003 Y 2Gruppe A 1005 Y 3Gruppe A 1007 N 1Gruppe A 1011 N 2Group A 1013 N 3Group A 1017 Y 1Group A 1019 Y 2Group A 1023 N 1Group A 1029 N 2Group B 1001 X 1 Group B 1002 x 2Group B 1003 Z 1Group B 1005 Z 2Group B 1008 Z 3Group B 1013 Z 4Group B 1021 Y 1Group B 1034 Y 2
Bemærk definitionen af den primære nøglebegrænsning baseret på den sammensatte nøgle (grp, ord), hvilket resulterer i et klynget indeks baseret på den samme nøgle. Dette indeks kan potentielt understøtte vinduesfunktioner opdelt af grp og ordnet efter ord.
For at teste din løsnings ydeevne skal du udfylde tabellen med større mængder eksempeldata. Brug følgende kode til at oprette hjælpefunktionen GetNums:
CREATE FUNCTION dbo.GetNums(@low AS BIGINT =1, @high AS BIGINT) RETURNERER TABLEASRETURN MED L0 AS (VÆLG 1 AS c FRA (VÆRDIER(1),(1),(1),(1), (1),(1),(1),(1), (1),(1),(1),(1),(1),(1),(1),(1)) AS D (c) ), L1 AS ( VÆLG 1 AS c FRA L0 SOM A CROSS JOIN L0 AS B ), L2 AS ( VÆLG 1 AS c FRA L1 SOM A CROSS JOIN L1 AS B ), L3 AS ( VÆLG 1 AS c FRA L2 SOM A CROSS JOIN L2 AS B ), Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum FROM L3 ) SELECT TOP(@high - @low + 1) rownum AS rn, @high + 1 - rownum AS op, @low - 1 + rownum AS n FROM Nums BESTIL EFTER rownum;GO
Brug følgende kode til at udfylde T1 efter at have ændret parametrene, der repræsenterer antallet af grupper, antallet af rækker pr. gruppe og antallet af distinkte værdier, som du ønsker:
DECLARE @numgroups AS INT =1000, @rowspergroup AS INT =10000, -- test med 1000 til 10000 her @uniquevalues AS INT =5; ÆNDRINGSTABEL dbo.T1 DROP BEGRÆNSNING PK_T1; TRUNCATE TABEL dbo.T1; INSERT INTO dbo.T1 WITH(TABLOCK) (grp, ord, val) SELECT CAST(G.n AS VARCHAR(10)) AS grp, CAST(R.n AS INT) AS ord, CAST(ABS(CHECKSUM(NEWID())) % @uniquevalues + 1 AS VARCHAR(10)) AS val FRA dbo.GetNums(1, @numgroups) AS G CROSS JOIN dbo.GetNums(1, @rowspergroup) AS R; ÆNDRINGSTABEL dbo.T1 TILFØJ BEGRÆNSNING PK_T1 PRIMÆR NØGLE KLUSTERET(grp, ord);
I mine præstationstests udfyldte jeg tabellen med 1.000 grupper, mellem 1.000 og 10.000 rækker pr. gruppe (altså 1M til 10M rækker) og 5 forskellige værdier. Jeg brugte en SELECT INTO
sætning for at skrive resultatet ind i en midlertidig tabel.
Min testmaskine har fire logiske CPU'er, der kører SQL Server 2019 Enterprise.
Jeg antager, at du bruger et miljø, der er designet til at understøtte batch-tilstand på rækkelager enten direkte, f.eks. ved at bruge SQL Server 2019 Enterprise-udgaven som min, eller indirekte ved at oprette et dummy-søjlelagerindeks på bordet.
Husk ekstra point, hvis det lykkes dig at komme med en effektiv løsning uden eksplicit sortering i planen. Held og lykke!
Er der behov for en sorteringsoperator til optimering af vinduesfunktioner?
Før jeg dækker løsninger, lidt optimeringsbaggrund, så det, du vil se i forespørgselsplanerne senere, vil give mere mening.
De mest almindelige teknikker til at løse ø-opgaver som vores involverer brug af en kombination af aggregat- og/eller rangeringsvinduefunktioner. SQL Server kan behandle sådanne vinduesfunktioner ved at bruge enten en række ældre rækketilstandsoperatorer (Segment, Sequence Project, Segment, Window Spool, Stream Aggregate) eller den nyere og normalt mere effektive batch-mode Window Aggregate-operator.
I begge tilfælde skal operatørerne, der håndterer vinduesfunktionens beregning, indlæse de data, der er bestilt af vinduesopdelings- og bestillingselementerne. Hvis du ikke har et understøttende indeks, skal SQL Server naturligvis introducere en sorteringsoperatør i planen. For eksempel, hvis du har flere vinduesfunktioner i din løsning med mere end én unik kombination af partitionering og bestilling, er du forpligtet til at have eksplicit sortering i planen. Men hvad hvis du kun har én unik kombination af partitionering og bestilling og et understøttende indeks?
Den ældre rækketilstandsmetode kan stole på forudbestilte data indtaget fra et indeks uden behov for en eksplicit sorteringsoperator i både seriel og parallel tilstand. Den nyere batchtilstandsoperatør eliminerer meget af ineffektiviteten ved den ældre rækketilstandsoptimering og har de iboende fordele ved batchtilstandsbehandling. Imidlertid kræver dens nuværende parallelle implementering en mellemliggende batch-mode parallel sorteringsoperatør, selv når et understøttende indeks er til stede. Kun dens serielle implementering kan stole på indeksrækkefølge uden en sorteringsoperator. Dette er alt at sige, når optimeringsværktøjet skal vælge en strategi til at håndtere din vinduesfunktion, forudsat at du har et understøttende indeks, vil det generelt være en af følgende fire muligheder:
- Rækketilstand, seriel, ingen sortering
- Rækketilstand, parallel, ingen sortering
- Batch-tilstand, seriel, ingen sortering
- Batch-tilstand, parallel, sortering
Uanset hvilken, der resulterer i den laveste planomkostning, vil der blive valgt, forudsat at forudsætningerne for parallelitet og batch-tilstand er opfyldt, når de overvejes. Normalt, for at optimeringsværktøjet kan retfærdiggøre en parallel plan, skal parallelitetsfordelene opveje det ekstra arbejde som trådsynkronisering. Med mulighed 4 ovenfor skal parallelitetsfordelene opveje det sædvanlige ekstra arbejde, der er forbundet med parallelisme, plus den ekstra sortering.
Mens jeg eksperimenterede med forskellige løsninger på vores udfordring, havde jeg tilfælde, hvor optimeringsværktøjet valgte mulighed 2 ovenfor. Den valgte det på trods af, at rækketilstandsmetoden indebærer nogle få ineffektiviteter, fordi fordelene ved parallelitet og ingen sortering resulterede i en plan med lavere omkostninger end alternativerne. I nogle af disse tilfælde resulterede fremtvingelse af en seriel plan med et tip i mulighed 3 ovenfor og i bedre ydeevne.
Med denne baggrund i tankerne, lad os se på løsninger. Jeg starter med to løsninger, der er afhængige af kendte teknikker til øopgaver, der ikke kan undslippe eksplicit sortering i planen.
Løsning baseret på kendt teknik 1
Følgende er den første løsning på vores udfordring, som er baseret på en teknik, der har været kendt i et stykke tid:
MED C AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY Ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS island FROM dbo.T1)SELECT grp, ord, val , ROW_NUMBER() OVER(OPDELING EFTER grp, val, ø BESTIL EFTER ord) SOM sekvensFRA C;
Jeg vil referere til det som kendt løsning 1.
CTE kaldet C er baseret på en forespørgsel, der beregner to rækkenumre. Den første (jeg vil referere til den som rn1) er partitioneret af grp og ordnet efter ord. Den anden (jeg vil referere til den som rn2) er opdelt af grp og val og ordnet efter ord. Da rn1 har mellemrum mellem forskellige grupper af samme val og rn2 ikke har, er forskellen mellem rn1 og rn2 (kolonne kaldet ø) en unik konstant værdi for alle rækker med samme grp- og valværdier. Følgende er resultatet af den indre forespørgsel, inklusive resultaterne af talberegninger med to rækker, som ikke returneres som separate kolonner:
grp ord val rn1 rn2 island-------- ----- ---- ---- ---- -------Gruppe A 1002 Y 1 1 0Gruppe A 1003 Y 2 2 0Gruppe A 1005 Y 3 3 0Gruppe A 1007 N 4 1 3Gruppe A 1011 N 5 2 3Gruppe A 1013 N 6 3 3Gruppe A 1017 Y 7 4 3Gruppe A 1019 G 1019 A 1019 G 9 A 5 1 G 0 5 5Gruppe B 1001 X 1 1 0Gruppe B 1002 X 2 2 0Gruppe B 1003 Z 3 1 2Gruppe B 1005 Z 4 2 2Gruppe B 1008 Z 5 3 2Gruppe B 1013 Group 1 G 4 G 1 B 1 G 4 G 1 B 1 G 4 G 1 G 1 G 1 G 4
Det, der er tilbage for den ydre forespørgsel at gøre, er at beregne resultatkolonnen seqno ved hjælp af funktionen ROW_NUMBER, opdelt efter grp, val og island, og ordnet efter ord, hvilket genererer det ønskede resultat.
Bemærk, at du kan få den samme ø-værdi for forskellige val-værdier inden for den samme partition, såsom i outputtet ovenfor. Derfor er det vigtigt at bruge grp, val og island som vinduespartitioner og ikke grp og island alene.
På samme måde, hvis du har at gøre med en ø-opgave, der kræver, at du grupperer dataene efter øen og beregner gruppeaggregater, vil du gruppere rækkerne efter grp, val og ø. Men det er ikke tilfældet med vores udfordring. Her fik du til opgave bare at beregne rækkenumre uafhængigt for hver ø.
Figur 1 har standardplanen, som jeg fik for denne løsning på min maskine efter at have udfyldt tabellen med 10M rækker.
Figur 1:Parallel plan for kendt løsning 1
Beregningen af rn1 kan stole på indeksrækkefølge. Så optimeringsværktøjet valgte strategien ingen sortering + parallel rækketilstand for denne (første segment- og sekvensprojektoperatører i planen). Da beregningerne af både rn2 og seqno bruger deres egne unikke kombinationer af partitionerings- og bestillingselementer, er eksplicit sortering uundgåelig for dem, uanset hvilken strategi der anvendes. Så optimeringsværktøjet valgte sortering + parallel batch-tilstandsstrategi for begge. Denne plan involverer to eksplicitte sorteringsoperatører.
I min præstationstest tog det denne løsning 3,68 sekunder at gennemføre mod 1 mio. rækker og 43,1 sekunder mod 10 mio. rækker.
Som nævnt testede jeg alle løsninger også ved at fremtvinge en seriel plan (med et MAXDOP 1-tip). Serieplanen for denne løsning er vist i figur 1.
Figur 2:Seriel plan for kendt løsning 1
Som forventet bruger beregningen af rn1 også denne gang batch-mode-strategien uden en forudgående sorteringsoperator, men planen har stadig to sorteringsoperatorer til de efterfølgende rækkenummerberegninger. Serieplanen klarede sig dårligere end den parallelle på min maskine med alle inputstørrelser, jeg testede, og det tog 4,54 sekunder at fuldføre med 1M rækker og 61,5 sekunder med 10M rækker.
Løsning baseret på kendt teknik 2
Den anden løsning, jeg vil præsentere, er baseret på en genial teknik til ø-detektion, som jeg lærte af Paul White for et par år siden. Følgende er den komplette løsningskode baseret på denne teknik:
MED C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(isstart) OVER(OPDELING EFTER grp RÆKKER EFTER ORD RÆKKER UBEGRÆNSET FOREGÅENDE) SOM ø FRA C1)VÆLG grp, ord, val, ROW_NUMBER() OVER(OPDELING ETTER grp, ø RÆKKER EFTER ord) SOM sekvensFRA C2;
Jeg vil referere til denne løsning som kendt løsning 2.
Forespørgslen, der definerer CTE C1-beregningerne, bruger et CASE-udtryk og LAG-vinduefunktionen (partitioneret af grp og ordnet efter ord) til at beregne en resultatkolonne kaldet isstart. Denne kolonne har værdien 0, når den aktuelle værdi er den samme som den foregående og 1 ellers. Med andre ord er det 1, når rækken er den første på en ø og 0 ellers.
Følgende er output fra forespørgslen, der definerer C1:
grp ord val isstart-------- ----- ---- --------Gruppe A 1002 Y 1Gruppe A 1003 Y 0Gruppe A 1005 Y 0Gruppe A 1007 N 1Gruppe A 1011 N 0Group A 1013 N 0Group A 1017 Y 1Group A 1019 Y 0Group A 1023 N 1 Gruppe A 1029 N 0Group B 1001 X 1 Group B 1002 X 0Group B 1003 Z 1Group B 1005 Z 0Group B 1008 Z 0Group B 1013 Z 0Group B 1021 Y Y Y Y 1Gruppe B 1034 Y 0
Magien, hvad angår ø-detektion, sker i CTE C2. Forespørgslen, der definerer den, beregner en ø-identifikator ved hjælp af SUM-vinduefunktionen (også opdelt efter grp og ordnet efter ord) anvendt på isstart-kolonnen. Resultatkolonnen med ø-id'et kaldes ø. Inden for hver partition får du 1 for den første ø, 2 for den anden ø, og så videre. Så kombinationen af kolonnerne grp og ø er en ø-id, som du kan bruge i ø-opgaver, der involverer gruppering efter ø, når det er relevant.
Følgende er output fra forespørgslen, der definerer C2:
grp ord val isstart island-------- ----- ---- ------------ -------Gruppe A 1002 Y 1 1Gruppe A 1003 Y 0 1Gruppe A 1005 Y 0 1Gruppe A 1007 N 1 2Gruppe A 1011 N 0 2Gruppe A 1013 N 0 2Gruppe A 1017 Y 1 3Gruppe A 1019 Y 0 3Gruppe A 104Group N 1023 N 1023 N 1 X 0 0 1 X 0 X 0 X 0 1Gruppe B 1003 Z 1 2Gruppe B 1005 Z 0 2Gruppe B 1008 Z 0 2Gruppe B 1013 Z 0 2Gruppe B 1021 Y 1 3Gruppe B 1034 Y 0 3
Til sidst beregner den ydre forespørgsel den ønskede resultatkolonne seqno med en ROW_NUMBER funktion, opdelt efter grp og island, og ordnet efter ord. Bemærk, at denne kombination af partitionerings- og bestillingselementer er forskellig fra den, der blev brugt af de tidligere vinduesfunktioner. Mens beregningen af de to første vinduesfunktioner potentielt kan stole på indeksrækkefølge, kan den sidste ikke.
Figur 3 har den standardplan, jeg fik for denne løsning.
Figur 3:Parallel plan for kendt løsning 2
Som du kan se i planen, bruger beregningen af de to første vinduesfunktioner strategien ingen sortering + parallel rækketilstand, og beregningen af den sidste bruger strategien sortering + parallel batchtilstand.
De køretider, jeg fik for denne løsning, varierede fra 2,57 sekunder mod 1 mio. rækker til 46,2 sekunder mod 10 mio. rækker.
Når jeg fremtvinger seriel behandling, fik jeg planen vist i figur 4.
Figur 4:Seriel plan for kendt løsning 2
Som forventet er alle vinduesfunktionsberegninger denne gang afhængige af batch-mode-strategien. De to første uden forudgående sortering, og de sidste med. Både den parallelle plan og den serielle involverede én eksplicit sorteringsoperator. Serieplanen klarede sig bedre end parallelplanen på min maskine med de inputstørrelser, jeg testede. De køretider, jeg fik for den tvungne serieplan, varierede fra 1,75 sekunder mod 1 mio. rækker til 21,7 sekunder mod 10 mio. rækker.
Løsning baseret på ny teknik
Da Erland introducerede denne udfordring i et privat forum, var folk skeptiske over for muligheden for at løse den med en forespørgsel, der var blevet optimeret med en plan uden eksplicit sortering. Det var alt, jeg havde brug for at høre for at motivere mig. Så her er, hvad jeg fandt på:
MED C1 AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY ord) AS rn, LAG(val) OVER(PARTITION BY grp ORDER BY ord) AS prev FROM dbo.T1),C2 AS( SELECT *, MAX(CASE WHEN val =prev THEN NULL ELSE rn END) OVER(PARTITION BY grp ORDER BY order ROWS UNBOUNDED PRECEDING) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoFROM C2;Løsningen bruger tre vinduesfunktioner:LAG, ROW_NUMBER og MAX. Det vigtige her er, at alle tre er baseret på grp-partitionering og ord-bestilling, som er tilpasset den understøttende indeksnøglerækkefølge.
Forespørgslen, der definerer CTE C1, bruger funktionen ROW_NUMBER til at beregne rækkenumre (rn kolonne), og LAG-funktionen til at returnere den forrige værdi (prev column).
Her er outputtet af forespørgslen, der definerer C1:
grp ord val rn prev-------- ----- ---- --- -----Gruppe A 1002 Y 1 NULLGroup A 1003 Y 2 YGroup A 1005 Y 3 YGroup A 1007 N 4 YGroup A 1011 N 5 NGroup A 1013 N 6 NGroup A 1017 Y 7 NGroup A 1019 Y 8 YGroup A 1023 N 9 YGroup A 1029 N 10 NGroup B 1001 B 1001 B 0G 3 B 1001 XG2 1005 Z 4 ZGroup B 1008 Z 5 ZGroup B 1013 Z 6 ZGroup B 1021 Y 7 ZGroup B 1034 Y 8 YBemærk, når val og prev er det samme, det er ikke den første række på øen, ellers er det det.
Baseret på denne logik bruger forespørgslen, der definerer CTE C2, et CASE-udtryk, der returnerer rn, når rækken er den første på en ø og NULL ellers. Koden anvender derefter MAX-vinduefunktionen på resultatet af CASE-udtrykket og returnerer den første rn af øen (første kolonne).
Her er outputtet af forespørgslen, der definerer C2, inklusive outputtet af CASE-udtrykket:
grp ord val rn prev CASE firstrn-------- ----- ---- --- ----- ----- --------Gruppe A 1002 Y 1 NULL 1 1Group A 1003 Y 2 Y NULL 1Group A 1005 Y 3 Y NULL 1Group A 1007 N 4 Y 4 4Group A 1011 N 5 N NULL 4Group A 1013 N 1013 N 1Group A 1013 N 1G 1G Y 8 Y NULL 7Gruppe A 1023 N 9 Y 9 9Gruppe A 1029 N 10 N NULL 9Gruppe B 1001 X 1 NULL 1 1Gruppe B 1002 X 2 X NULL 1Gruppe B 1003 Z 0Gro Z 04 Z 0G 3 Z 0 Z 0 B 0 G 4 5 Z NULL 3Gruppe B 1013 Z 6 Z NULL 3Gruppe B 1021 Y 7 Z 7 7Gruppe B 1034 Y 8 Y NULL 7Det, der er tilbage til den ydre forespørgsel, er at beregne den ønskede resultatkolonne seqno som rn minus firstrn plus 1.
Figur 5 har den parallelle standardplan, jeg fik for denne løsning, da jeg testede den ved hjælp af en SELECT INTO-sætning, hvor jeg skrev resultatet ind i en midlertidig tabel.
Figur 5:Parallel plan for en ny løsning
Der er ingen eksplicitte sorteringsoperatører i denne plan. Imidlertid er alle tre vinduesfunktioner beregnet ved hjælp af strategien ingen sortering + parallel række, så vi mangler fordelene ved batchbehandling. De køretider, jeg fik for denne løsning med parallelplanen, varierede fra 2,47 sekunder mod 1M rækker og 41,4 mod 10M rækker.
Her er der ret stor sandsynlighed for, at en seriel plan med batchbehandling klarer sig markant bedre, især når maskinen ikke har mange CPU'er. Husk, at jeg tester mine løsninger mod en maskine med 4 logiske CPU'er. Figur 6 har den plan, jeg fik for denne løsning, når jeg skulle fremtvinge seriel behandling.
Figur 6:Seriel plan for en ny løsning
Alle tre vinduesfunktioner bruger strategien ingen sortering + seriel batch-tilstand. Og resultaterne er ret imponerende. Løsningens kørselstider varierede fra 0,5 sekunder mod 1M rækker og 5,49 sekunder mod 10M rækker. Hvad der også er nysgerrig ved denne løsning er, at når man tester den som en normal SELECT-sætning (med resultater kasseret) i modsætning til en SELECT INTO-sætning, valgte SQL Server den serielle plan som standard. Med de to andre løsninger fik jeg en parallel plan som standard både med SELECT og med SELECT INTO.
Se det næste afsnit for de komplette resultater af præstationstest.
Sammenligning af ydeevne
Her er koden, jeg brugte til at teste de tre løsninger, selvfølgelig fjerne MAXDOP 1-tipset for at teste serieplanerne:
-- Test kendt løsning 1 SLIP TABEL HVIS FINDER #Resultat; MED C AS( SELECT *, ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY Ord) - ROW_NUMBER() OVER(PARTITION BY grp, val ORDER BY ord) AS ø FRA dbo.T1)SELECT grp, ord, val, ROW_NUMBER( ) OVER(OPDELING EFTER grp, val, ø BESTIL EFTER ord) SOM sekvensINTO #ResultatFRA C/* OPTION(MAXDOP 1) */; -- uncomment for seriel testGO -- Test kendt løsning 2 SLIP TABEL HVIS EKSISTERER #Resultat; MED C1 AS( SELECT *, CASE WHEN val =LAG(val) OVER(PARTITION BY grp ORDER BY ord) THEN 0 ELSE 1 END AS isstart FROM dbo.T1),C2 AS( SELECT *, SUM(erstart) OVER(PARTITION) BY grp ORDER BY ORD ROWS UNboundED PRECEDING) AS island FROM C1)SELECT grp, ord, val, ROW_NUMBER() OVER(PARTITION BY GRP, ø ORDER BY Ord) AS seqnoINTO #ResultFROM C2/* OPTION(MAXDOP 1) */; GO -- Test ny løsning SLIP TABEL HVIS EKSISTERER #Resultat; MED C1 AS( SELECT *, ROW_NUMBER() OVER(OPDELING EFTER grp ORDER BY ord) AS rn, LAG(val) OVER(OPDELING EFTER grp ORDER BY ord) SOM forrige FRA dbo.T1),C2 AS( SELECT *, MAX (CASE WHEN val =prev THEN NULL ELSE rn END) OVER(OPDELING EFTER grp ORDER BY ORD RÆKKER UNBOUNDED PRECEDING) AS firstrn FROM C1)SELECT grp, ord, val, rn - firstrn + 1 AS seqnoINTO #ResultFROM C2/* OPTION( MAXDOP 1) */;Figur 7 viser kørselstiderne for både de parallelle og serielle planer for alle løsninger mod forskellige inputstørrelser.
Figur 7:Præstationssammenligning
Den nye løsning, der bruger seriel tilstand, er den klare vinder. Den har fantastisk ydeevne, lineær skalering og øjeblikkelig responstid.
Konklusion
Ø-opgaver er ret almindelige i det virkelige liv. Mange af dem involverer både identifikation af øer og gruppering af data efter øen. Erlands ø-udfordring, som var fokus i denne artikel, er en smule mere unik, fordi den ikke involverer gruppering, men i stedet sekventering af hver øs rækker med rækkenumre.
Jeg præsenterede to løsninger baseret på kendte teknikker til at identificere øer. Problemet med begge er, at de resulterer i planer, der involverer eksplicit sortering, hvilket negativt påvirker løsningernes ydeevne, skalerbarhed og responstid. Jeg præsenterede også en ny teknik, der resulterede i en plan uden sortering overhovedet. Den serielle plan for denne løsning, som bruger en no sort + seriel batch mode-strategi, har fremragende ydeevne, lineær skalering og øjeblikkelig responstid. Det er uheldigt, i det mindste for nu, at vi ikke kan have en no sort + parallel batch mode-strategi til håndtering af vinduesfunktioner.