W tym artykule pokazano, w jaki sposób SQL Server łączy informacje o gęstości z wielu jednokolumnowych statystyk w celu oszacowania liczności dla agregacji w wielu kolumnach. Miejmy nadzieję, że szczegóły same w sobie są interesujące. Zapewniają również wgląd w niektóre ogólne podejścia i algorytmy stosowane przez estymator kardynalności.
Rozważ następującą przykładową kwerendę bazy danych AdventureWorks, która zawiera listę pozycji zapasów produktów w każdym pojemniku na każdej półce w magazynie:
SELECT INV.Shelf, INV.Bin, COUNT_BIG(*) FROM Production.ProductInventory AS INV GROUP BY INV.Shelf, INV.Bin ORDER BY INV.Shelf, INV.Bin;
Szacowany plan wykonania pokazuje 1069 wierszy odczytanych z tabeli, posortowanych na Shelf
i Bin
zamówienia, a następnie agregowane za pomocą operatora Stream Aggregate:
Szacowany plan wykonania
Pytanie brzmi, w jaki sposób optymalizator zapytań SQL Server uzyskał ostateczne oszacowanie 744 wierszy?
Dostępne statystyki
Podczas kompilowania powyższego zapytania optymalizator zapytań utworzy jednokolumnowe statystyki na Shelf
i Bin
kolumny, jeśli odpowiednie statystyki jeszcze nie istnieją. Statystyki te dostarczają między innymi informacji o liczbie różnych wartości kolumn (w wektorze gęstości):
DBCC SHOW_STATISTICS ( [Production.ProductInventory], [Shelf] ) WITH DENSITY_VECTOR; DBCC SHOW_STATISTICS ( [Production.ProductInventory], [Bin] ) WITH DENSITY_VECTOR;
Wyniki podsumowano w poniższej tabeli (trzecia kolumna jest obliczana na podstawie gęstości):
Kolumna | Gęstość | 1 / Gęstość |
---|---|---|
Półka | 0.04761905 | 21 |
Kosz | 0.01612903 | 62 |
Informacje o wektorach gęstości półek i pojemników
Jak zauważa dokumentacja, odwrotnością gęstości jest liczba różnych wartości w kolumnie. Z informacji statystycznych przedstawionych powyżej, SQL Server wie, że było 21 różnych Shelf
wartości i 62 różne Bin
wartości w tabeli, kiedy zebrano statystyki.
Zadanie oszacowania liczby wierszy utworzonych przez GROUP BY
klauzula jest trywialna, gdy w grę wchodzi tylko jedna kolumna (zakładając, że nie ma innych predykatów). Na przykład łatwo zauważyć, że GROUP BY Shelf
wyprodukuje 21 rzędów; GROUP BY Bin
wyprodukuje 62.
Jednak nie jest od razu jasne, w jaki sposób SQL Server może oszacować liczbę odrębnych (Shelf, Bin)
kombinacje dla naszych półek GROUP BY Shelf, Bin
zapytanie. Ujmując to pytanie w nieco inny sposób:biorąc pod uwagę 21 półek i 62 kosze, ile będzie unikalnych kombinacji półek i koszy? Pomijając aspekty fizyczne i inną ludzką wiedzę na temat dziedziny problemu, odpowiedź może zawierać się w dowolnym miejscu od max(21, 62) =62 do (21 * 62) =1,302. Bez dodatkowych informacji nie ma oczywistego sposobu, aby dowiedzieć się, gdzie podać oszacowanie w tym zakresie.
Jednak dla naszego przykładowego zapytania SQL Server szacuje 744.312 wiersze (zaokrąglone do 744 w widoku Eksploratora planów), ale na jakiej podstawie?
Rozszerzone wydarzenie szacowania kardynalności
Udokumentowanym sposobem przyjrzenia się procesowi szacowania liczności jest użycie zdarzenia rozszerzonego query_optimizer_estimate_cardinality
(pomimo tego, że jest w kanale "debugowania"). Podczas sesji zbierającej to zdarzenie operatorzy planu wykonania zyskują dodatkową właściwość StatsCollectionId
która łączy indywidualne szacunki operatora z obliczeniami, które je stworzyły. W naszym przykładowym zapytaniu identyfikator zbioru statystyk 2 jest połączony z oszacowaną licznością dla grupy za pomocą operatora agregacji.
Odpowiednie dane wyjściowe z zdarzenia rozszerzonego dla naszego zapytania testowego to:
<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>
Na pewno jest tam kilka przydatnych informacji.
Widzimy, że plan pozostawia klasę kalkulatora różnych wartości (CDVCPlanLeaf
) został wykorzystany przy użyciu statystyk jednokolumnowych na Shelf
i Bin
jako wejścia. Element zbierania statystyk dopasowuje ten fragment do identyfikatora (2) pokazanego w planie wykonania, który pokazuje szacunkową kardynalność wynoszącą 744.31 , a także więcej informacji o użytych identyfikatorach obiektów statystyk.
Niestety, w danych wyjściowych zdarzenia nie ma nic, co mogłoby dokładnie powiedzieć, w jaki sposób kalkulator osiągnął ostateczną wartość, co nas naprawdę interesuje.
Łączenie różnych liczb
Idąc mniej udokumentowaną trasą, możemy poprosić o szacunkowy plan zapytania z flagami śledzenia 2363 i 3604 włączone:
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);
Spowoduje to zwrócenie informacji debugowania na kartę Komunikaty w programie SQL Server Management Studio. Ciekawa część jest reprodukowana poniżej:
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
Pokazuje to w dużej mierze te same informacje, co zdarzenie rozszerzone, w (prawdopodobnie) łatwiejszym do wykorzystania formacie:
- Wejściowy operator relacyjny do obliczenia szacunkowej liczności dla (
LogOp_GbAgg
– grupowanie logiczne według agregatu) - Użyty kalkulator (
CDVCPlanLeaf
) i statystyki wejściowe - Szczegóły zebranych statystyk wynikowych
Ciekawą nową informacją jest część dotycząca używania kardynalności otoczenia do łączenia odrębnych liczb .
To wyraźnie pokazuje, że użyto wartości 21, 62 i 1069, ale (frustrująco) nadal nie do końca dokładnie, które obliczenia zostały wykonane, aby uzyskać 744.312 wynik.
Do debugera!
Dołączenie debugera i użycie symboli publicznych pozwala nam szczegółowo zbadać ścieżkę kodu, którą podążamy podczas kompilowania przykładowego zapytania.
Poniższy zrzut ekranu pokazuje górną część stosu wywołań w reprezentatywnym punkcie procesu:
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
Jest tu kilka interesujących szczegółów. Pracując od dołu do góry, widzimy, że liczność jest obliczana przy użyciu zaktualizowanego CE (CCardFrameworkSQL12
) dostępne w SQL Server 2014 i nowszych (oryginalny CE to CCardFrameworkSQL7
), dla grupy przez zagregowany operator logiczny (CLogOp_GbAgg
).
Obliczanie odrębnej kardynalności polega na łączeniu (mungingu) wielu danych wejściowych przy użyciu próbkowania bez zastępowania.
Odniesienie do H
a logarytm (naturalny) w drugiej metodzie od góry pokazuje użycie Entropii Shannona w obliczeniach:
Entropia Shannona
Entropię można wykorzystać do oszacowania korelacji informacyjnej (informacji wzajemnych) między dwiema statystykami:
Wzajemne informacje
Łącząc to wszystko razem, możemy skonstruować skrypt obliczeniowy T-SQL pasujący do sposobu, w jaki SQL Server używa próbkowania bez zastępowania, entropii Shannona i wzajemnych informacji aby uzyskać ostateczne oszacowanie kardynalności.
Zaczynamy od liczb wejściowych (liczność otoczenia i liczba odrębnych wartości w każdej kolumnie):
DECLARE @Card float = 1069, @Distinct1 float = 21, @Distinct2 float = 62;
Częstotliwość każdej kolumny to średnia liczba wierszy na odrębną wartość:
DECLARE @Frequency1 float = @Card / @Distinct1, @Frequency2 float = @Card / @Distinct2;
Próbkowanie bez zastępowania (SWR) to prosta kwestia odjęcia średniej liczby wierszy na odrębną wartość (częstotliwość) od całkowitej liczby wierszy:
DECLARE @SWR1 float = @Card - @Frequency1, @SWR2 float = @Card - @Frequency2, @SWR3 float = @Card - @Frequency1 - @Frequency2;
Oblicz entropie (N log N) i wzajemne informacje:
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);
Teraz oszacowaliśmy, jak skorelowane są te dwa zestawy statystyk, możemy obliczyć ostateczne oszacowanie:
SELECT (1e0 - @MI) * @Distinct1 * @Distinct2;
Wynik obliczenia to 744.311823994677, czyli 744.312 zaokrąglone do trzech miejsc po przecinku.
Dla wygody, oto cały kod w jednym bloku:
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;
Ostateczne myśli
Ostateczne oszacowanie jest w tym przypadku niedoskonałe – przykładowe zapytanie faktycznie zwraca 441 wiersze.
Aby uzyskać lepsze oszacowanie, możemy dostarczyć optymalizatorowi lepsze informacje o gęstości Bin
i Shelf
kolumny przy użyciu statystyki wielokolumnowej. Na przykład:
CREATE STATISTICS stat_Shelf_Bin ON Production.ProductInventory (Shelf, Bin);
Mając tę statystykę (jako podaną, albo jako efekt uboczny dodania podobnego indeksu wielokolumnowego), oszacowanie kardynalności dla przykładowego zapytania jest dokładnie poprawne. Rzadko jednak zdarza się obliczyć tak prostą agregację. Z dodatkowymi predykatami statystyka wielokolumnowa może być mniej skuteczna. Niemniej jednak ważne jest, aby pamiętać, że dodatkowe informacje o gęstości dostarczane przez statystyki wielokolumnowe mogą być przydatne do agregacji (jak również do porównań równości).
Bez statystyki wielokolumnowej zapytanie agregujące z dodatkowymi predykatami może nadal korzystać z podstawowej logiki przedstawionej w tym artykule. Na przykład, zamiast stosować formułę do liczności tabeli, można ją zastosować do wprowadzania histogramów krok po kroku.
Treść powiązana:Szacowanie liczności dla predykatu w wyrażeniu COUNT