Ograniczę ten post do omawiania statystyk z jedną kolumną, ponieważ będzie on już dość długi, a ty jesteś zainteresowany tym, jak SQL Server grupuje dane w etapy histogramu. W przypadku statystyk wielokolumnowych histogram jest tworzony tylko w kolumnie wiodącej.
Gdy SQL Server stwierdzi, że potrzebna jest aktualizacja statystyk, uruchamia ukryte zapytanie, które odczytuje wszystkie dane tabeli lub próbkę danych tabeli. Możesz przeglądać te zapytania z rozszerzonymi zdarzeniami. Istnieje funkcja wywoływana StatMan
w SQL Server, która jest zaangażowana w tworzenie histogramów. W przypadku prostych obiektów statystycznych istnieją co najmniej dwa różne typy StatMan
zapytań (istnieją różne zapytania do szybkich aktualizacji statystyk i podejrzewam, że funkcja statystyki przyrostowej w tabelach partycjonowanych również używa innego zapytania).
Pierwszy pobiera wszystkie dane z tabeli bez filtrowania. Możesz to zobaczyć, gdy stół jest bardzo mały lub zbierasz statystyki z FULLSCAN
opcją:
CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);
-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
SELECT TOP 100 PERCENT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
ORDER BY [SC0]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 16);
SQL Server wybiera automatyczny rozmiar próbki na podstawie wielkości tabeli (myślę, że jest to zarówno liczba wierszy, jak i stron w tabeli). Jeśli tabela jest zbyt duża, automatyczny rozmiar próbki spada poniżej 100%. Oto, co dostałem dla tej samej tabeli z 1 milionami wierszy:
-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM
(
SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000]
FROM
(
SELECT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY [SC0], [SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);
TABLESAMPLE
jest udokumentowany, ale StatMan i krok_kierunek nie są. tutaj SQL Server próbkuje około 66,6% danych z tabeli w celu utworzenia histogramu. Oznacza to, że podczas aktualizacji statystyk (bez FULLSCAN
) dla tych samych danych można uzyskać inną liczbę kroków histogramu . Nigdy nie obserwowałem tego w praktyce, ale nie rozumiem, dlaczego nie byłoby to możliwe.
Przeprowadźmy kilka testów na prostych danych, aby zobaczyć, jak statystyki zmieniają się w czasie. Poniżej znajduje się kod testowy, który napisałem, aby wstawić sekwencyjne liczby całkowite do tabeli, zebrać statystyki po każdej wstawce i zapisać informacje o statystykach w tabeli wyników. Zacznijmy od wstawienia 1 rzędu na raz do 10000. Łóżko testowe:
DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE X_STATS_RESULTS;
SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
SELECT @stats_id = stats_id FROM sys.stats
WHERE OBJECT_ID = @table_object_id
AND name = 'X_STATS_SEQ_INT_FULL';
SET @loop_num = 0;
WHILE @loop_num < @num_of_loops
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed
INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps
FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
END;
END;
Dla tych danych liczba kroków histogramu szybko wzrasta do 200 (najpierw osiąga maksymalną liczbę kroków z 397 wierszami), pozostaje na poziomie 199 lub 200, aż 1485 wierszy znajdzie się w tabeli, a następnie powoli maleje, aż histogram ma tylko 3 lub 4 kroki. Oto wykres wszystkich danych:
Oto histogram dla 10 000 wierszy:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 1 0 1
9999 9997 1 9997 1
10000 0 1 0 1
Czy to problem, że histogram składa się tylko z 3 kroków? Wygląda na to, że informacje są przechowywane z naszego punktu widzenia. Zauważ, że ponieważ typem danych jest INTEGER, możemy dowiedzieć się, ile wierszy znajduje się w tabeli dla każdej liczby całkowitej od 1 - 10000. Zwykle SQL Server również może to rozgryźć, chociaż są przypadki, w których to się nie sprawdza . Zobacz ten post SE na przykład.
Jak myślisz, co się stanie, jeśli usuniemy pojedynczy wiersz z tabeli i zaktualizujemy statystyki? Idealnie byłoby uzyskać kolejny krok histogramu, aby pokazać, że brakująca liczba całkowita nie jest już w tabeli.
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
To trochę rozczarowuje. Gdybyśmy budowali histogram ręcznie, dodalibyśmy krok dla każdej brakującej wartości. SQL Server korzysta z algorytmu ogólnego przeznaczenia, więc w przypadku niektórych zestawów danych możemy opracować bardziej odpowiedni histogram niż używany przez niego kod. Oczywiście praktyczna różnica między uzyskaniem 0 lub 1 rzędu ze stołu jest bardzo mała. Otrzymuję te same wyniki podczas testowania z 20000 wierszami, przy czym każda liczba całkowita ma 2 wiersze w tabeli. Histogram nie zyskuje kroków, gdy usuwam dane.
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 2 0 1
9999 19994 2 9997 2
10000 0 2 0 1
Jeśli wykonam test z 1 milionem wierszy z każdą liczbą całkowitą mającą 100 wierszy w tabeli, otrzymam nieco lepsze wyniki, ale nadal mogę ręcznie stworzyć lepszy histogram.
truncate table X_SEQ_NUM;
BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps
Ostateczny histogram:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 100 0 1
998 99600 100 996 100
3983 298100 100 2981 100
9999 600900 100 6009 100
10000 0 100 0 1
Przetestujmy dalej za pomocą liczb całkowitych sekwencyjnych, ale z większą liczbą wierszy w tabeli. Zauważ, że dla tabel, które są zbyt małe, ręczne określenie wielkości próbki nie przyniesie żadnego efektu, więc dodam 100 wierszy do każdej wstawki i zbieram statystyki za każdym razem do 1 miliona wierszy. Widzę podobny wzór jak poprzednio, tyle że po przejściu do 637300 wierszy w tabeli nie próbuję już 100% wierszy w tabeli z domyślną częstotliwością próbkowania. Gdy zdobywam wiersze, liczba kroków histogramu rośnie. Być może dzieje się tak, ponieważ w programie SQL Server pojawia się więcej luk w danych, gdy rośnie liczba niespróbkowanych wierszy w tabeli. Nie uderzam w 200 kroków nawet przy 1 M rzędach, ale jeśli nadal będę dodawał rzędy, spodziewam się, że tam dotrę i w końcu zacznę wracać.
Oś X to liczba wierszy w tabeli. W miarę wzrostu liczby wierszy próbkowane wiersze różnią się nieco i nie przekraczają 650 tys.
Teraz zróbmy kilka prostych testów z danymi VARCHAR.
CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);
Tutaj wstawiam 200 liczb (jako ciągów) wraz z NULL.
INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps
Zauważ, że NULL zawsze otrzymuje własny krok histogramu, gdy zostanie znaleziony w tabeli. SQL Server mógł dać mi dokładnie 201 kroków, aby zachować wszystkie informacje, ale tego nie zrobił. Informacje techniczne są tracone, ponieważ „1111” sortuje na przykład między „1” a „2”.
Teraz spróbujmy wstawić różne znaki zamiast tylko liczb całkowitych:
truncate table X_SEQ_STR;
INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2
Brak rzeczywistej różnicy od ostatniego testu.
Teraz spróbujmy wstawiać znaki, ale umieszczać różne liczby każdego znaku w tabeli. Na przykład CHAR(11)
ma 1 wiersz, CHAR(12)
ma 2 rzędy itp.
truncate table X_SEQ_STR;
DECLARE
@loop_num INT;
BEGIN
SET NOCOUNT ON;
SET @loop_num = 0;
WHILE @loop_num < 200
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_STR WITH (TABLOCK)
SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
END;
END;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0
Tak jak poprzednio nadal nie otrzymuję dokładnie 200 kroków histogramu. Jednak wiele kroków ma wartość RANGE_ROWS
0.
W końcowym teście wstawię losowy ciąg 5 znaków w każdej pętli i za każdym razem zbieram statystyki. Oto kod losowego ciągu:
char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))
Oto wykres wierszy w tabeli względem kroków histogramu:
Pamiętaj, że liczba kroków nie spada poniżej 100, gdy zacznie rosnąć i spadać. Słyszałem skądś (ale nie mogę go teraz zdobyć), że algorytm budowania histogramu SQL Server łączy kroki histogramu, gdy kończy się dla nich miejsce. Możesz więc skończyć z drastycznymi zmianami liczby kroków, dodając trochę danych. Oto jedna próbka danych, które uznałem za interesujące:
ROWS_IN_TABLE ROWS_SAMPLED STEPS
36661 36661 133
36662 36662 143
36663 36663 143
36664 36664 141
36665 36665 138
Nawet przy próbkowaniu z FULLSCAN
dodaniem pojedynczego wiersza można zwiększyć liczbę kroków o 10, utrzymywać go na stałym poziomie, następnie zmniejszyć o 2, a następnie zmniejszyć o 3.
Co możemy z tego podsumować? Nie mogę tego udowodnić, ale te obserwacje wydają się być prawdziwe:
- SQL Server używa ogólnego algorytmu do tworzenia histogramów. W przypadku niektórych dystrybucji danych może być możliwe ręczne utworzenie pełniejszej reprezentacji danych.
- Jeśli w tabeli znajdują się dane NULL, a zapytanie statyczne je znajdzie, to dane NULL zawsze otrzymują własny krok histogramu.
- Minimalna wartość znaleziona w tabeli otrzymuje własny krok histogramu z
RANGE_ROWS
= 0.
- Maksymalna wartość znaleziona w tabeli będzie ostateczną wartością
RANGE_HI_KEY
w tabeli.
- Gdy SQL Server próbkuje więcej danych, może być konieczne połączenie istniejących kroków, aby zrobić miejsce dla nowych danych, które znajdzie. Jeśli spojrzysz na wystarczającą liczbę histogramów, możesz zobaczyć powtarzające się wspólne wartości dla
DISTINCT_RANGE_ROWS
lub RANGE_ROWS
. Na przykład 255 pokazuje wiele razy dla RANGE_ROWS
i DISTINCT_RANGE_ROWS
dla ostatniego przypadku testowego tutaj.
- W przypadku prostych dystrybucji danych SQL Server łączy sekwencyjne dane w jeden krok histogramu, który nie powoduje utraty informacji. Jednak podczas dodawania luk do danych histogram może nie dostosować się w sposób, jaki można by oczekiwać.
Kiedy to wszystko stanowi problem? Jest to problem, gdy zapytanie działa słabo z powodu histogramu, który nie jest w stanie reprezentować rozkładu danych w sposób, który może pomóc w podejmowaniu dobrych decyzji przez optymalizator zapytań. Myślę, że istnieje tendencja do myślenia, że posiadanie większej liczby kroków histogramu jest zawsze lepsze i może istnieć konsternacja, gdy SQL Server generuje histogram na milionach wierszy lub więcej, ale nie używa dokładnie 200 lub 201 kroków histogramu. Jednak widziałem wiele problemów ze statystykami, nawet jeśli histogram ma 200 lub 201 kroków. Nie mamy żadnej kontroli nad tym, ile kroków histogramu generuje SQL Server dla obiektu statystyki, więc nie martwię się o to. Istnieją jednak pewne kroki, które można wykonać, gdy wystąpią zapytania o niskiej wydajności spowodowane problemami ze statystykami. Dam bardzo krótki przegląd.
W niektórych przypadkach pomocne może być pełne gromadzenie statystyk. W przypadku bardzo dużych tabel wielkość automatycznej próbki może być mniejsza niż 1% wierszy w tabeli. Czasami może to prowadzić do złych planów w zależności od zakłóceń danych w kolumnie. Dokumentacja Microsofts dla CREATE STATISTICS i UPDATE STATISTICS mówi tyle samo:
SAMPLE jest przydatny w szczególnych przypadkach, w których plan zapytań oparty na domyślnym próbkowaniu nie jest optymalny. W większości sytuacji nie jest konieczne określanie PRÓBKI, ponieważ optymalizator zapytań już używa próbkowania i domyślnie określa statystycznie znaczący rozmiar próbki, zgodnie z wymaganiami dotyczącymi tworzenia wysokiej jakości planów zapytań.
W przypadku większości obciążeń pełne skanowanie nie jest wymagane, a domyślne próbkowanie jest wystarczające. Jednak niektóre obciążenia wrażliwe na bardzo różne dystrybucje danych mogą wymagać zwiększenia wielkości próbki lub nawet pełnego skanowania.
W niektórych przypadkach pomocne może być tworzenie filtrowanych statystyk. Możesz mieć kolumnę z wypaczonymi danymi i wieloma różnymi odrębnymi wartościami. Jeśli w danych często filtrowane są określone wartości, można utworzyć histogram statystyczny tylko dla tych wspólnych wartości. Optymalizator zapytań może korzystać ze statystyk zdefiniowanych dla mniejszego zakresu danych zamiast statystyk zdefiniowanych dla wszystkich wartości kolumn. Nadal nie masz gwarancji, że uzyskasz 200 kroków na histogramie, ale jeśli utworzysz filtrowane statystyki tylko dla jednej wartości, histogram krok tej wartości.
Korzystanie z widoku podzielonego na partycje jest jednym ze sposobów skutecznego uzyskania ponad 200 kroków dla tabeli. Załóżmy, że możesz łatwo podzielić duży stół na jeden stół rocznie. Tworzysz UNION ALL
widok, który łączy wszystkie tabele roczne. Każda tabela będzie miała własny histogram. Należy pamiętać, że nowe statystyki przyrostowe wprowadzone w SQL Server 2014 pozwalają tylko na bardziej wydajne aktualizacje statystyk. Optymalizator zapytań nie będzie korzystał ze statystyk tworzonych dla poszczególnych partycji.
Istnieje wiele innych testów, które można uruchomić tutaj, więc zachęcam do eksperymentowania. Zrobiłem te testy na SQL Server 2014 express, więc naprawdę nic Cię nie powstrzymuje.