sql >> Database teknologi >  >> RDS >> Database

Kardinalitetsvurdering:Kombination af tæthedsstatistik

Denne artikel viser, hvordan SQL Server kombinerer tæthedsoplysninger fra flere enkeltkolonnestatistikker for at producere et kardinalitetsestimat for en aggregering over flere kolonner. Detaljerne er forhåbentlig interessante i sig selv. De giver også et indblik i nogle af de generelle tilgange og algoritmer, der bruges af kardinalitetsestimatoren.

Overvej følgende AdventureWorks-eksempeldatabaseforespørgsel, som viser antallet af produktbeholdningsartikler i hver beholder på hver hylde i lageret:

SELECT 
    INV.Shelf, 
    INV.Bin, 
    COUNT_BIG(*)
FROM Production.ProductInventory AS INV
GROUP BY 
    INV.Shelf,
    INV.Bin
ORDER BY 
    INV.Shelf,
    INV.Bin;

Den estimerede udførelsesplan viser 1.069 rækker, der læses fra tabellen, sorteret i Shelf og Bin rækkefølge, derefter aggregeret ved hjælp af en Stream Aggregate-operator:

Estimeret udførelsesplan

Spørgsmålet er, hvordan nåede SQL Server-forespørgselsoptimeringsværktøjet frem til det endelige estimat på 744 rækker?

Tilgængelig statistik

Når forespørgslen ovenfor kompileres, vil forespørgselsoptimeringsværktøjet oprette enkeltkolonnestatistik på Shelf og Bin kolonner, hvis der ikke allerede findes passende statistik. Disse statistikker giver blandt andet information om antallet af distinkte kolonneværdier (i tæthedsvektoren):

DBCC SHOW_STATISTICS 
(
    [Production.ProductInventory], 
    [Shelf]
)
WITH DENSITY_VECTOR;
 
DBCC SHOW_STATISTICS 
(
    [Production.ProductInventory], 
    [Bin]
)
WITH DENSITY_VECTOR;

Resultaterne er opsummeret i tabellen nedenfor (den tredje kolonne er beregnet ud fra tætheden):

Kolonne Tæthed 1 / Tæthed
hylde 0,04761905 21
Bin 0,01612903 62

Vektoroplysninger om hylde- og beholdertæthed

Som dokumentationen bemærker, er den gensidige af tætheden antallet af distinkte værdier i kolonnen. Fra de statistiske oplysninger vist ovenfor ved SQL Server, at der var 21 forskellige Shelf værdier og 62 forskellige Bin værdier i tabellen, hvornår statistikken blev indsamlet.

Opgaven med at estimere antallet af rækker produceret af en GROUP BY klausul er triviel, når kun en enkelt kolonne er involveret (forudsat ingen andre prædikater). For eksempel er det nemt at se, at GROUP BY Shelf vil producere 21 rækker; GROUP BY Bin vil producere 62.

Det er dog ikke umiddelbart klart, hvordan SQL Server kan estimere antallet af distinkte (Shelf, Bin) kombinationer for vores GROUP BY Shelf, Bin forespørgsel. For at stille spørgsmålet på en lidt anden måde:Givet 21 hylder og 62 skraldespande, hvor mange unikke kombinationer af hylde og skraldespande vil der være? Ser man bort fra fysiske aspekter og anden menneskelig viden om problemdomænet, kan svaret være alt fra max(21, 62) =62 til (21 * 62) =1.302. Uden mere information er der ingen indlysende måde at vide, hvor man skal pitche et estimat i det interval.

Men for vores eksempelforespørgsel estimerer SQL Server 744.312 rækker (afrundet til 744 i Plan Explorer-visningen), men på hvilket grundlag?

Kardinalitetsvurdering udvidet hændelse

Den dokumenterede måde at se på kardinalitetsestimeringsprocessen på er at bruge den udvidede hændelse query_optimizer_estimate_cardinality (på trods af at det er i "debug"-kanalen). Mens en session, der indsamler denne hændelse, kører, får eksekveringsplanoperatører en ekstra egenskab StatsCollectionId der knytter individuelle operatørestimater til de beregninger, der har produceret dem. For vores eksempelforespørgsel er statistikindsamlings-id 2 knyttet til kardinalitetsestimatet for gruppen efter aggregeret operatør.

Det relevante output fra den udvidede begivenhed for vores testforespørgsel er:

<data name="calculator">
    <type name="xml" package="package0"></type>
    <value>
    <CalculatorList>
        <DistinctCountCalculator CalculatorName="CDVCPlanLeaf" SingleColumnStat="Shelf,Bin" />
    </CalculatorList>
    </value>
</data>
<data name="stats_collection">
    <type name="xml" package="package0"></type>
    <value>
    <StatsCollection Name="CStCollGroupBy" Id="2" Card="744.31">
        <LoadedStats>
        <StatsInfo DbId="6" ObjectId="258099960" StatsId="3" />
        <StatsInfo DbId="6" ObjectId="258099960" StatsId="4" />
        </LoadedStats>
    </StatsCollection>
    </value>
</data>

Der er helt sikkert nogle nyttige oplysninger der.

Vi kan se, at regnemaskineklassen for planblads distinkte værdier (CDVCPlanLeaf ) blev brugt ved at bruge enkelt kolonne statistik på Shelf og Bin som input. Statistikindsamlingselementet matcher dette fragment med id'et (2) vist i udførelsesplanen, som viser kardinalitetsestimatet på 744.31 , og mere information om de anvendte statistiske objekt-id'er.

Desværre er der intet i begivenhedens output, der siger præcis, hvordan lommeregneren nåede frem til det endelige tal, hvilket er det, vi virkelig er interesserede i.

Kombinering af forskellige tæller

Går vi en mindre dokumenteret rute, kan vi anmode om en estimeret plan for forespørgslen med sporingsflag 2363 og 3604 aktiveret:

SELECT 
    INV.Shelf, 
    INV.Bin, 
    COUNT_BIG(*)
FROM Production.ProductInventory AS INV
GROUP BY 
    INV.Shelf,
    INV.Bin
ORDER BY 
    INV.Shelf,
    INV.Bin
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363);

Dette returnerer fejlfindingsoplysninger til fanen Meddelelser i SQL Server Management Studio. Den interessante del er gengivet nedenfor:

Begin distinct values computation
Input tree:
  LogOp_GbAgg OUT(QCOL: [INV].Shelf,QCOL: [INV].Bin,COL: Expr1001 ,) BY(QCOL: [INV].Shelf,QCOL: [INV].Bin,)
      CStCollBaseTable(ID=1, CARD=1069 TBL: Production.ProductInventory AS TBL: INV)
      AncOp_PrjList 
          AncOp_PrjEl COL: Expr1001 
              ScaOp_AggFunc stopCountBig
                  ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=0)

Plan for computation:
  CDVCPlanLeaf
      0 Multi-Column Stats, 2 Single-Column Stats, 0 Guesses

Loaded histogram for column QCOL: [INV].Shelf from stats with id 3
Loaded histogram for column QCOL: [INV].Bin from stats with id 4

Using ambient cardinality 1069 to combine distinct counts:
  21
  62

Combined distinct count: 744.312
Result of computation: 744.312

Stats collection generated: 
  CStCollGroupBy(ID=2, CARD=744.312)
      CStCollBaseTable(ID=1, CARD=1069 TBL: Production.ProductInventory AS TBL: INV)

End distinct values computation

Dette viser stort set den samme information, som den udvidede begivenhed gjorde i et (formentlig) lettere at forbruge format:

  • Input relationsoperatoren til at beregne et kardinalitetsestimat for (LogOp_GbAgg – logisk gruppe efter aggregat)
  • Den anvendte lommeregner (CDVCPlanLeaf ) og inputstatistik
  • De resulterende statistikindsamlingsdetaljer

Den interessante nye information er den del, der handler om at bruge omgivende kardinalitet til at kombinere forskellige tal .
Dette viser tydeligt, at værdierne 21, 62 og 1069 blev brugt, men (frustrerende nok) stadig ikke nøjagtigt, hvilke beregninger der blev udført for at nå frem til 744.312 resultat.

Til Debugger!

Ved at vedhæfte en debugger og bruge offentlige symboler kan vi i detaljer udforske den kodesti, som følges under kompileringen af ​​eksempelforespørgslen.
Snapshottet nedenfor viser den øverste del af opkaldsstakken på et repræsentativt punkt i processen:

MSVCR120!log
sqllang!OdblNHlogN
sqllang!CCardUtilSQL12::ProbSampleWithoutReplacement
sqllang!CCardUtilSQL12::CardDistinctMunged
sqllang!CCardUtilSQL12::CardDistinctCombined
sqllang!CStCollAbstractLeaf::CardDistinctImpl
sqllang!IStatsCollection::CardDistinct
sqllang!CCardUtilSQL12::CardGroupByHelperCore
sqllang!CCardUtilSQL12::PstcollGroupByHelper
sqllang!CLogOp_GbAgg::PstcollDeriveCardinality
sqllang!CCardFrameworkSQL12::DeriveCardinalityProperties

Der er et par interessante detaljer her. Når vi arbejder nedefra og op, ser vi, at kardinalitet udledes ved hjælp af den opdaterede CE (CCardFrameworkSQL12 ) tilgængelig i SQL Server 2014 og senere (den originale CE er CCardFrameworkSQL7 ), for gruppen efter aggregeret logisk operator (CLogOp_GbAgg ).

At beregne den distinkte kardinalitet involverer at kombinere (munging) flere input, ved at bruge sampling uden erstatning.

Referencen til H og en (naturlig) logaritme i den anden metode fra toppen viser brugen af ​​Shannon Entropy i beregningen:

Shannon-entropi

Entropi kan bruges til at estimere informationskorrelationen (gensidig information) mellem to statistikker:

Gensidig information

Ved at sætte alt dette sammen kan vi konstruere et T-SQL-beregningsscript, der matcher den måde, SQL Server bruger sampling uden erstatning, Shannon Entropy og gensidig information at producere det endelige kardinalitetsestimat.

Vi starter med inputtallene (omgivende kardinalitet og antallet af distinkte værdier i hver kolonne):

DECLARE 
    @Card float = 1069,
    @Distinct1 float = 21,
    @Distinct2 float = 62;

frekvensen af hver kolonne er det gennemsnitlige antal rækker pr. særskilt værdi:

DECLARE
    @Frequency1 float = @Card / @Distinct1,
    @Frequency2 float = @Card / @Distinct2;

Sampling uden erstatning (SWR) er et simpelt spørgsmål om at trække det gennemsnitlige antal rækker pr. distinkt værdi (frekvens) fra det samlede antal rækker:

DECLARE
    @SWR1 float = @Card - @Frequency1,
    @SWR2 float = @Card - @Frequency2,
    @SWR3 float = @Card - @Frequency1 - @Frequency2;

Beregn entropierne (N log N) og gensidig information:

DECLARE
    @E1 float = (@SWR1 + 0.5) * LOG(@SWR1),
    @E2 float = (@SWR2 + 0.5) * LOG(@SWR2),
    @E3 float = (@SWR3 + 0.5) * LOG(@SWR3),
    @E4 float = (@Card + 0.5) * LOG(@Card);
 
-- Using logarithms allows us to express
-- multiplication as addition and division as subtraction
DECLARE
    @MI float = EXP(@E1 + @E2 - @E3 - @E4);

Nu har vi estimeret, hvor korrelerede de to sæt statistikker er, vi kan beregne det endelige estimat:

SELECT (1e0 - @MI) * @Distinct1 * @Distinct2;

Resultatet af beregningen er 744.311823994677, hvilket er 744.312 afrundet til tre decimaler.

For nemheds skyld er her hele koden i én blok:

DECLARE 
    @Card float = 1069,
    @Distinct1 float = 21,
    @Distinct2 float = 62;
 
DECLARE
    @Frequency1 float = @Card / @Distinct1,
    @Frequency2 float = @Card / @Distinct2;
 
-- Sample without replacement
DECLARE
    @SWR1 float = @Card - @Frequency1,
    @SWR2 float = @Card - @Frequency2,
    @SWR3 float = @Card - @Frequency1 - @Frequency2;
 
-- Entropy
DECLARE
    @E1 float = (@SWR1 + 0.5) * LOG(@SWR1),
    @E2 float = (@SWR2 + 0.5) * LOG(@SWR2),
    @E3 float = (@SWR3 + 0.5) * LOG(@SWR3),
    @E4 float = (@Card + 0.5) * LOG(@Card);
 
-- Mutual information
DECLARE
    @MI float = EXP(@E1 + @E2 - @E3 - @E4);
 
-- Final estimate
SELECT (1e0 - @MI) * @Distinct1 * @Distinct2;

Sidste tanker

Det endelige estimat er ufuldkomment i dette tilfælde – eksempelforespørgslen returnerer faktisk 441 rækker.

For at opnå et forbedret estimat kunne vi give optimeringsværktøjet bedre oplysninger om tætheden af ​​Bin og Shelf kolonner ved hjælp af en multikolonnestatistik. For eksempel:

CREATE STATISTICS stat_Shelf_Bin 
ON Production.ProductInventory (Shelf, Bin);

Med denne statistik på plads (enten som givet, eller som en bivirkning af at tilføje et lignende multikolonneindeks), er kardinalitetsestimatet for eksempelforespørgslen nøjagtigt korrekt. Det er dog sjældent at beregne en så simpel aggregering. Med yderligere prædikater kan multikolonnestatistikken være mindre effektiv. Ikke desto mindre er det vigtigt at huske, at de yderligere tæthedsoplysninger, der leveres af multikolonnestatistikker, kan være nyttige til aggregeringer (såvel som lighedssammenligninger).

Uden en multikolonnestatistik kan en samlet forespørgsel med yderligere prædikater muligvis stadig bruge den væsentlige logik, der er vist i denne artikel. For eksempel, i stedet for at anvende formlen på tabellens kardinalitet, kan den anvendes til at indtaste histogrammer trin for trin.

Relateret indhold:Kardinalitetsvurdering for et prædikat på et COUNT-udtryk


  1. Escape en streng i SQL Server, så den er sikker at bruge i LIKE udtryk

  2. Er det muligt at bruge Full Text Search (FTS) med LINQ?

  3. Guide til design af database til medarbejderstyringssystem i MySQL

  4. I SQL Server, hvordan man pivoterer for flere kolonner