sql >> Databasteknik >  >> RDS >> Database

Kardinalitetsuppskattning för ett predikat på ett COUNT-uttryck

Den här artikeln undersöker selektivitets- och kardinalitetsuppskattning för predikat på COUNT(*) uttryck, som kan ses i HAVING klausuler. Detaljerna är förhoppningsvis intressanta i sig. De ger också en inblick i några av de allmänna tillvägagångssätt och algoritmer som används av kardinalitetsskattaren.

Ett enkelt exempel med hjälp av exempeldatabasen AdventureWorks:

SELECT A.City
  FROM Person.[Address] AS A
  GROUP BY A.City
  HAVING COUNT_BIG(*) = 1;

Vi är intresserade av att se hur SQL Server härleder en uppskattning för predikatet på count-uttrycket i HAVING klausul.

Naturligtvis HAVING klausul är bara syntax socker. Vi kunde likaså ha skrivit frågan med en härledd tabell, eller vanligt tabelluttryck:

-- Derived table
SELECT SQ1.City
FROM
(
    SELECT A.City, Expr1001 = COUNT_BIG(*)
    FROM Person.[Address] AS A
    GROUP BY A.City
) AS SQ1
WHERE SQ1.Expr1001 = 1;
 
-- CTE
WITH Grouped AS
(
    SELECT A.City, Expr1001 = COUNT_BIG(*)
    FROM Person.[Address] AS A
    GROUP BY A.City
)
SELECT G.City
FROM Grouped AS G
WHERE G.Expr1001 = 1;

Alla tre frågeformulär producerar samma exekveringsplan, med identiska hashvärden för frågeplan.

Planen efter genomförandet (faktisk) visar en perfekt uppskattning för aggregatet; dock uppskattningen för HAVING klausulfiltret (eller motsvarande, i de andra frågeformulären) är dåligt:

Statistik för City kolumnen ger korrekt information om antalet distinkta stadsvärden:

DBCC SHOW_STATISTICS ([Person.Address], City) WITH DENSITY_VECTOR;

all densitet siffran är det ömsesidiga antalet unika värden. Beräknar helt enkelt (1 / 0,00173913) =575 ger kardinalitetsuppskattningen för aggregatet. Gruppering efter stad ger uppenbarligen en rad för varje distinkt värde.

Observera att all densitet kommer från densitetsvektorn. Var noga med att inte använda densiteten av misstag värde från statistikhuvudets utdata för DBCC SHOW_STATISTICS . Rubrikdensiteten bibehålls endast för bakåtkompatibilitet; den används inte av optimeraren under kardinalitetsuppskattning nu för tiden.

Problemet

Aggregatet introducerar en ny beräknad kolumn i arbetsflödet, märkt Expr1001 i genomförandeplanen. Den innehåller värdet COUNT(*) i varje grupperad utdatarad:

Det finns uppenbarligen ingen statistisk information i databasen om denna nya beräknade kolumn. Även om optimeraren vet att det kommer att finnas 575 rader, vet den ingenting om distributionen av räknevärden inom dessa rader.

Tja, inte riktigt ingenting:Optimeraren är medveten om att räknevärdena kommer att vara positiva heltal (1, 2, 3...). Ändå är det fördelningen av dessa heltalsvärden mellan de 575 raderna som skulle behövas för att exakt uppskatta selektiviteten för COUNT(*) = 1 predikat.

Man skulle kunna tro att någon form av distributionsinformation skulle kunna härledas från histogrammet, men histogrammet ger bara specifik räkningsinformation (i EQ_ROWS ) för histogramstegvärden. Mellan histogramstegen har vi bara en sammanfattning:RANGE_ROWS rader har DISTINCT_RANGE_ROWS distinkta värden. För tabeller som är tillräckligt stora för att vi bryr oss om kvaliteten på selektivitetsuppskattningen är det mycket troligt att större delen av tabellen representeras av dessa sammanfattningar inom steg.

Till exempel, de två första raderna i City kolumnhistogrammen är:

DBCC SHOW_STATISTICS ([Person.Address], City) WITH HISTOGRAM;

Detta säger oss att det finns exakt en rad för "Abingdon" och 29 andra rader efter "Abingdon" men före "Ballard", med 19 distinkta värden i det 29-radsintervallet. Följande fråga visar den faktiska fördelningen av rader mellan unika värden i det intervallet med 29 rader inom steg:

SELECT A.City, NumRows = COUNT_BIG(*)
FROM Person.[Address] AS A 
WHERE A.City > N'Abingdon' 
AND A.City < N'Ballard'
GROUP BY ROLLUP (A.City);

Det finns 29 rader med 19 distinkta värden, precis som histogrammet sa. Ändå är det tydligt att vi inte har någon grund för att utvärdera selektiviteten för ett predikat på räknekolumnen i den frågan. Till exempel, HAVING COUNT_BIG(*) = 2 skulle returnera 5 rader (för Alexandria, Altadena, Atlanta, Augsburg och Austin) men vi har inget sätt att avgöra det från histogrammet.

En utbildad gissning

Tillvägagångssättet för SQL Server är att anta att varje grupp är mest troligt för att innehålla det totala genomsnittliga (genomsnittliga) antalet rader. Detta är helt enkelt kardinaliteten dividerad med antalet unika värden. Till exempel, för 1000 rader med 20 unika värden, skulle SQL Server anta att (1000 / 20) =50 rader per grupp är det mest sannolika värdet.

Om vi ​​vänder tillbaka till vårt ursprungliga exempel betyder detta att kolumnen för beräknat antal "mest troligt" innehåller ett värde runt (19614 / 575) ~=34.1113 . Sedan density är det ömsesidiga antalet unika värden, kan vi också uttrycka det som kardinalitet * densitet =(19614 * 0,00173913), vilket ger ett mycket liknande resultat.

Distribution

Att säga att medelvärdet är mest troligt tar oss bara så långt. Vi måste också fastställa exakt hur troligt det är; och hur sannolikheten förändras när vi går bort från medelvärdet. Att anta att alla grupper har exakt 34.113 rader i vårt exempel skulle inte vara en särskilt "utbildad" gissning!

SQL Server hanterar detta genom att anta en normalfördelning. Denna har den karakteristiska klockformen som du kanske redan känner till (bild från den länkade Wikipedia-posten):

Den exakta formen på normalfördelningen beror på två parametrar :medelvärdet (µ ) och standardavvikelsen (σ ). Medelvärdet bestämmer platsen för toppen. Standardavvikelsen anger hur "tillplattad" klockkurvan är. Ju plattare kurva är, desto lägre är toppen, och desto mer fördelas sannolikhetstätheten över andra värden.

SQL Server kan härleda medelvärdet från statistisk information som redan noterats. Standardavvikelsen av kolumnvärdena för beräknat antal är okända. SQL Server uppskattar det som kvadratroten av medelvärdet (med en liten justering i detalj senare). I vårt exempel betyder det att de två parametrarna för normalfördelningen är ungefär 34,1113 och 5,84 (kvadratroten).

standarden normalfördelning (den röda kurvan i diagrammet ovan) är ett anmärkningsvärt specialfall. Detta inträffar när medelvärdet är noll och standardavvikelsen är 1. Vilken normalfördelning som helst kan omvandlas till standardnormalfördelningen genom att subtrahera medelvärdet och dividera med standardavvikelsen.

Ytor och intervall

Vi är intresserade av att uppskatta selektivitet, så vi letar efter sannolikheten att den räknade kolumnen har ett visst värde (x). Denna sannolikhet ges inte av y-axelns värde ovan, utan av arean under kurvan till vänster om x.

För normalfördelningen med medelvärde 34,1113 och standardavvikelse 5,84 är arean under kurvan till vänster om x =30 cirka 0,2406:

Detta motsvarar sannolikheten att kolumnen för beräknat antal är mindre än eller lika med 30 för vår exempelfråga.

Detta leder bra vidare till idén att vi i allmänhet inte letar efter sannolikheten för ett specifikt värde, utan efter ett intervall . För att hitta sannolikt att antalet lika ett heltalsvärde måste vi ta hänsyn till det faktum att heltal spänner över ett intervall av storlek 1. Hur vi omvandlar ett heltal till ett intervall är något godtyckligt. SQL Server hanterar detta genom att lägga till och subtrahera 0,5 för att ge de nedre och övre gränserna för intervallet.

Till exempel, för att hitta sannolikheten att det beräknade räknevärdet är lika med 30, måste vi subtrahera arean under normalfördelningskurvan för (x =29,5) från arean för (x =30,5). Resultatet motsvarar segmentet för (29,5

Arean av den röda skivan är ungefär 0,0533 . Till en bra första approximation är detta selektiviteten för ett count =30 predikat i vår testfråga.

Den kumulativa distributionsfunktionen

Att beräkna arean under en normalfördelning till vänster om ett givet värde är inte okomplicerat. Den allmänna formeln ges av den kumulativa fördelningsfunktionen (CDF). Problemet är att CDF inte kan uttryckas i termer av elementära matematiska funktioner, så numeriska approximationsmetoder måste användas istället.

Eftersom alla normalfördelningar lätt kan transformeras till standardnormalfördelningen (medelvärde =0, standardavvikelse =1), fungerar alla approximationer för att uppskatta standardnormalen. Det betyder att vi måste omvandla våra intervallgränser från den specifika normalfördelningen som är lämplig för frågan, till standardnormalfördelningen. Detta görs, som tidigare nämnt, genom att subtrahera medelvärdet och dividera med standardavvikelsen.

Om du är bekant med Excel kanske du känner till funktionerna NORM.DIST och NORM.S.DIST som kan beräkna CDF:er (med numeriska approximationsmetoder) för en viss normalfördelning eller standardnormalfördelningen.

Det finns ingen CDF-kalkylator inbyggd i SQL Server, men vi kan enkelt göra en. Med tanke på att CDF för standardnormalfördelningen är:

…där erf är felfunktionen:

En T-SQL-implementering för att erhålla CDF för normal normalfördelning visas nedan. Den använder en numerisk uppskattning för felfunktionen som är mycket nära den som SQL Server använder internt:

CREATE PROCEDURE dbo.GetStandardNormalCDF
(
    @x float,
    @cdf float OUTPUT
)
AS
BEGIN
    SET NOCOUNT, XACT_ABORT ON;
    DECLARE
        @sign float,
        @erf float;
 
    SET @sign = SIGN(@x);
    SET @x = ABS(@x) / SQRT(2);
    SET @erf = 1;
    SET @erf = @erf + (0.0705230784 * @x);
    SET @erf = @erf + (0.0422820123 * POWER(@x, 2));
    SET @erf = @erf + (0.0092705272 * POWER(@x, 3));
    SET @erf = @erf + (0.0001520143 * POWER(@x, 4));
    SET @erf = @erf + (0.0002765672 * POWER(@x, 5)); 
    SET @erf = @erf + (0.0000430638 * POWER(@x, 6));
    SET @erf = POWER(@erf, -16);
    SET @erf = 1 - @erf;
    SET @erf = @erf * @sign;
    SET @cdf = 0.5 * (1 + @erf);
END;

Ett exempel, för att beräkna CDF för x =30 med normalfördelningen för vår testfråga:

DECLARE @cdf float;
DECLARE @x float;
-- HAVING COUNT_BIG(*) = x
SET @x = 30;
-- Normalize 30 by subtracting the mean
-- and dividing by the standard deviation
SET @x = (@x - 34.1113) / 5.84;
EXECUTE dbo.GetStandardNormalCDF
    @x = @x,
    @cdf = @cdf OUTPUT;
SELECT CDF = @cdf;

Notera normaliseringssteget för att konvertera till standardnormalfördelningen. Proceduren returnerar värdet 0,2407196…, vilket matchar motsvarande Excel-resultat med sju decimaler.

Sluta detaljer och exempel

Följande kod modifierar vår exempelfråga för att producera en större uppskattning för filtret (jämförelsen är nu med värdet 32, vilket är mycket närmare medelvärdet än tidigare):

SELECT A.City
FROM Person.[Address] AS A
GROUP BY A.City
HAVING COUNT_BIG(*) = 32;

Uppskattningen från optimeraren är nu 36,7807 .

För att beräkna uppskattningen manuellt måste vi först ta itu med några sista detaljer:

  • Medelvärdet som används för att härleda standardavvikelsen (via kvadratroten) skalas med en faktor på ((särskilda värden – 1) / (särskilda värden) . I exemplet är antalet distinkta värden 575, så skalfaktorn är (574 / 575) ~=0,99826.
  • Om den nedre gränsen för (heltals)intervallet är 1, behandlar SQL Server intervallet som obegränsat på undersidan. Selektiviteten är lika med CDF för den övre gränsen för intervallet (1,5) enbart. Den nedre gränsen (som skulle vara 0,5) används inte.
  • Den äldre kardinalitetsuppskattaren (CE) har komplex logik för COUNT(*) = 1 , som inte beskrivs i detalj här.
  • Bortsett från COUNT(*) = 1 fall använder den äldre CE:n samma logik som den nya CE:n (tillgänglig i SQL Server 2014 och framåt).

Följande procedur innehåller alla detaljer i den här artikeln. Det kräver CDF-proceduren som gavs tidigare:

CREATE PROCEDURE dbo.GetCountPredicateEstimate
(
    @From           integer,
    @To             integer,
    @Cardinality    float,
    @Density        float,
    @Selectivity    float OUTPUT,
    @Estimate       float OUTPUT
)
AS
BEGIN
    SET NOCOUNT, XACT_ABORT ON;
    BEGIN TRY
    DECLARE
        @Start          float,
        @End            float,
        @Distinct       float,
        @Mean           float,
        @MeanAdj        float,
        @Stdev          float,
        @NormStart      float,
        @NormEnd        float,
        @CDFStart       float,
        @CDFEnd         float;
    -- Validate input and apply defaults
    IF ISNULL(@From, 0) = 0 SET @From = 1;
    IF @From < 1 RAISERROR ('@From must be >= 1', 16, 1);
    IF ISNULL(@Cardinality, -1) <= 0 RAISERROR('@Cardinality must be positive', 16, 1);
    IF ISNULL(@Density, -1) <= 0 RAISERROR('@Density must be positive', 16, 1);
    IF ISNULL(@To, 0) = 0 SET @To = CEILING(1 / @Density);
    IF @To < @From RAISERROR('@To must be >= @From', 16, 1);
    -- Convert integer range to interval
    SET @Start = @From - 0.5;
    SET @End = @To + 0.5;
    -- Get number of distinct values
    SET @Distinct = 1 / @Density;
    -- Calculate mean
    SET @Mean = @Cardinality * @Density;
    -- Adjust mean;
    SET @MeanAdj = @Mean * ((@Distinct - 1) / @Distinct);
    -- Get standard deviation (guess)
    SET @Stdev = SQRT(@MeanAdj);
    -- Normalize interval
    SET @NormStart = (@Start - @Mean) / @Stdev;
    SET @NormEnd = (@End - @Mean) / @Stdev;
    -- Calculate CDFs
    EXECUTE dbo.GetStandardNormalCDF
        @x = @NormStart,
        @cdf = @CDFStart OUTPUT;
 
    EXECUTE dbo.GetStandardNormalCDF
        @x = @NormEnd,
        @cdf = @CDFEnd OUTPUT;
    -- Selectivity
    SET @Selectivity =
        CASE
            -- Unbounded start
            WHEN @From = 1 THEN @CDFEnd
            -- Unbounded end
            WHEN @To >= @Distinct THEN 1 - @CDFStart
            -- Normal interval
            ELSE @CDFEnd - @CDFStart
        END;
    -- Return row estimate
    SET @Estimate = @Selectivity * @Distinct;
    END TRY
    BEGIN CATCH
        DECLARE @EM nvarchar(4000) = ERROR_MESSAGE();
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        RAISERROR (@EM, 16, 1);
        RETURN;
    END CATCH;
END;

Vi kan nu använda denna procedur för att generera en uppskattning för vår nya testfråga:

DECLARE 
    @Selectivity float,
    @Estimate float;
EXECUTE dbo.GetCountPredicateEstimate
    @From = 32,
    @To = 32,
    @Cardinality = 19614,
    @Density = 0.00173913,
    @Selectivity = @Selectivity OUTPUT,
    @Estimate = @Estimate OUTPUT;
SELECT
    Selectivity = @Selectivity,
    Estimate = @Estimate,
    Rounded = ROUND(@Estimate, 4);

Utdata är:

Detta kan jämföras mycket bra med optimerarens kardinalitetsuppskattning på 36,7807.

Exempel på ojämlikhetsintervall

Förfarandet kan användas för andra räkneintervall förutom jämställdhetstest. Allt som krävs är att ställa in @From och @To parametrar till heltalsintervallgränserna. För att ange obegränsad, skicka noll eller NULL som du föredrar.

SELECT A.City
FROM Person.[Address] AS A
GROUP BY A.City
HAVING COUNT_BIG(*) < 50;

För att använda detta med vår procedur ställer vi in ​​@From = NULL och @To = 49 (eftersom 50 exkluderas med mindre än):

DECLARE 
    @Selectivity float,
    @Estimate float;
EXECUTE dbo.GetCountPredicateEstimate
    @From = NULL,
    @To = 49,
    @Cardinality = 19614,
    @Density = 0.00173913,
    @Selectivity = @Selectivity OUTPUT,
    @Estimate = @Estimate OUTPUT;
SELECT
    Selectivity = @Selectivity,
    Estimate = @Estimate,
    Rounded = ROUND(@Estimate, 4);

Resultatet är 572.5964:

Ett sista exempel med BETWEEN :

SELECT A.City
FROM Person.[Address] AS A
GROUP BY A.City
HAVING COUNT_BIG(*) BETWEEN 25 AND 30;

Optimeringsuppskattningen är

Sedan BETWEEN är inklusive, klarar vi proceduren @From = 25 och @To = 30 . Resultatet är:

Återigen stämmer detta överens med optimeringsuppskattningen.


  1. Hur använder man LOAD_FILE för att ladda en fil till en MySQL-blob?

  2. Hur man tar bort en databas i cPanel

  3. Så här visar du flera frågor och resultat sida vid sida i SQL Server Management Studio (SSMS) - SQL Server / TSQL självstudie del 14

  4. Sammanfoga/sammanfoga matrisvärden under gruppering/aggregering