Kiedy kilka tygodni temu byłem w Chicago na jednym z naszych Immersion Events, jeden z uczestników miał pytanie statystyczne. Nie będę omawiał wszystkich szczegółów dotyczących tego problemu, ale uczestnik wspomniał, że statystyki zostały zaktualizowane za pomocą sp_updatestats
. Jest to metoda aktualizacji statystyk, której nigdy nie polecałem; Zawsze zalecałem kombinację przebudowy indeksu i UPDATE STATISTICS
aktualizować statystyki. Jeśli nie znasz sp_updatestats
, jest to polecenie uruchamiane dla całej bazy danych w celu aktualizacji statystyk. Ale jak Kimberly wskazała uczestnikowi, sp_updatestats
zaktualizuje statystykę, o ile zmodyfikowano jeden wiersz. Łał. Natychmiast otworzyłem Books Online i dla sp_updatestats
zobaczysz to:
Przyznaję, że założyłem, co oznacza „…wymagaj aktualizacji na podstawie informacji o rowmodctr w widoku katalogu sys.sysindexes…”. Założyłem, że decyzja o aktualizacji będzie przebiegać zgodnie z tą samą logiką, co opcja automatycznej aktualizacji statystyk, czyli:
- Rozmiar tabeli spadł z 0 do>0 wierszy (test 1).
- Liczba wierszy w tabeli, w której zebrano statystyki, wynosiła 500 lub mniej, a colmodctr wiodącej kolumny obiektu statystyk zmienił się od tego czasu o ponad 500 (test 2).
- Tabela miała więcej niż 500 wierszy podczas zbierania statystyk, a colmodctr wiodącej kolumny obiektu statystyk zmienił się o ponad 500 + 20% liczby wierszy w tabeli podczas zbierania statystyk ( test 3).
Ta logika nie jest przestrzegana dla sp_updatestats
. W rzeczywistości logika jest tak niewiarygodnie prosta, że aż przerażająca:jeśli jeden wiersz zostanie zmodyfikowany, statystyki zostaną zaktualizowane. Jeden rząd. JEDEN RZĄD. Jakie mam obawy? Martwię się o koszty aktualizacji statystyk dla wielu statystyk, które tak naprawdę nie muszą być aktualizowane. Przyjrzyjmy się bliżej sp_updatestats
.
Zaczniemy od nowej kopii bazy danych AdventureWorks2012, którą można pobrać z Codeplex. Najpierw zaktualizuję wiersze w trzech różnych tabelach:
USE [AdventureWorks2012]; GO SET NOCOUNT ON; GO UPDATE [Production].[Product] SET [Name] = 'Bike Chain' WHERE [ProductID] = 952; UPDATE [Person].[Person] SET [LastName] = 'Cameron' WHERE [LastName] = 'Diaz'; GO INSERT INTO Sales.SalesReason (Name, ReasonType, ModifiedDate) VALUES('Stats', 'Test', GETDATE()); GO 10000
Zmodyfikowaliśmy jeden wiersz w Production.Product
, 211 wierszy w Person.Person
i dodaliśmy 10 000 wierszy do Sales.SalesReason
. Jeśli sp_updatestats
procedura postępuje zgodnie z tą samą logiką dla aktualizacji, co opcja automatycznej aktualizacji statystyk, a następnie tylko Sales.SalesReason
zaktualizowałby się, ponieważ miał 10 wierszy na początek (podczas gdy 211 wierszy zostało zaktualizowanych w Person.Person
stanowią około jednego procenta tabeli). Jeśli jednak zagłębimy się w sp_updatestats
, widzimy, że zastosowana logika jest inna. Pamiętaj, że wyciągam tylko instrukcje z sp_updatestats
które są używane do określenia, jakie statystyki są aktualizowane.
Kursor przechodzi przez wszystkie tabele zdefiniowane przez użytkownika i tabele wewnętrzne w bazie danych:
declare ms_crs_tnames cursor local fast_forward read_only for select name, object_id, schema_id, type from sys.objects o where o.type = 'U' or o.type = 'IT' open ms_crs_tnames fetch next from ms_crs_tnames into @table_name, @table_id, @sch_id, @table_type
Kolejny kursor przegląda statystyki dla każdej tabeli i wyklucza sterty oraz hipotetyczne indeksy i statystyki. Zauważ, że sys.sysindexes
jest używany w sp_helpstats
. Sysindexes
jest tabelą systemową programu SQL Server 2000, która zostanie usunięta w przyszłej wersji programu SQL Server. Jest to interesujące, ponieważ inną metodą określania zaktualizowanych wierszy jest sys.dm_db_stats_properties
DMF, który jest dostępny tylko w SQL 2008 R2 SP2 i SQL 2012 SP1.
set @index_names = cursor local fast_forward read_only for select name, indid, rowmodctr from sys.sysindexes where id = @table_id and indid > 0 and indexproperty(id, name, 'ishypothetical') = 0 order by indid
Po odrobinie przygotowań i dodatkowej logice dochodzimy do IF
oświadczenie, które ujawnia, że sp_updatestats
odfiltrowuje statystyki, dla których nie zaktualizowano żadnych wierszy… potwierdzając, że nawet jeśli tylko jeden wiersz został zmodyfikowany, statystyka zostanie zaktualizowana. Jest też czek na @is_ver_current
, który jest określony przez wbudowaną, wewnętrzną funkcję.
if ((@ind_rowmodctr <> 0) or ((@is_ver_current is not null) and (@is_ver_current = 0)))
Jeszcze kilka sprawdzeń związanych z próbkowaniem i poziomem zgodności, a następnie UPDATE
instrukcja jest wykonywana dla statystyki. Zanim faktycznie uruchomimy sp_updatestats, możemy wykonać zapytanie sys.sysindexes
aby zobaczyć, jakie statystyki zostaną zaktualizowane:
SELECT [o].[name], [si].[indid], [si].[name], [si].[rowmodctr], [si].[rowcnt], [o].[type] FROM [sys].[objects] [o] JOIN [sys].[sysindexes] [si] ON [o].[object_id] = [si].[id] WHERE ([o].[type] = 'U' OR [o].[type] = 'IT') AND [si].[indid] > 0 AND [si].[rowmodctr] <> 0 ORDER BY [o].[type] DESC, [o].[name];
Oprócz trzech zmodyfikowanych przez nas tabel istnieje jeszcze jedna statystyka dotycząca tabeli użytkownika (dbo.DatabaseLog
) oraz trzy wewnętrzne statystyki, które zostaną zaktualizowane:
Statystyki, które zostaną zaktualizowane
Jeśli uruchomimy sp_updatestats
w przypadku bazy danych AdventureWorks dane wyjściowe zawierają listę wszystkich tabel i zaktualizowanych statystyk. Poniższe dane wyjściowe zostały zmodyfikowane, aby wyświetlać tylko zaktualizowane statystyki:
Aktualizacja [sys].[fulltext_avdl_1589580701]
[clust] został zaktualizowany…
1 indeks(y)/statystyka(e) zostały zaktualizowane, 0 nie wymagało aktualizacji.
…
Aktualizacja [dbo].[DatabaseLog]
[PK_DatabaseLog_DatabaseLogID] został zaktualizowany…
1 indeks(y)/statystyki zostały zaktualizowane, 0 nie wymagało aktualizacji.
…
Aktualizacja [sys].[fulltext_avdl_1077578877]
[clust] został zaktualizowany…
1 indeks(y)/statystyki zostały zaktualizowane, 0 nie wymagało aktualizacji.
…
Aktualizowanie [Osoba].[Osoba]
[PK_Person_BusinessEntityID], aktualizacja nie jest konieczna…
[IX_Person_LastName_FirstName_MiddleName] została zaktualizowana…
[AK_Person_rowguid], aktualizacja nie jest konieczna…
1 indeks(y)/statystyka(e) zostały zaktualizowane, 2 nie wymagały aktualizacji.
…
Aktualizacja [Sales].[SalesReason]
[PK_SalesReason_SalesReasonID] został zaktualizowany…
1 indeks(y)/statystyki zostały zaktualizowane, 0 nie wymagało aktualizacji.
…
Aktualizacja [Produkcja].[Produkt]
[PK_Product_ID], aktualizacja nie jest konieczna…
[AK_Product_ProductNumber], aktualizacja nie jest konieczna…
[AK_Product_Name] został zaktualizowany…
[ AK_Product_rowguid], aktualizacja nie jest konieczna...
[_WA_Sys_00000013_75A278F5], aktualizacja nie jest konieczna...
[_WA_Sys_00000014_75A278F5], aktualizacja nie jest konieczna...
[_WA_Sys_0000000D_75A278F5], aktualizacja nie jest konieczna>[_WA_Sys_0000000C_75A278F5], aktualizacja nie jest konieczna…
1 indeks(y)/statystyki zostały zaktualizowane, 7 nie wymagało aktualizacji.
…
Statystyki dla wszystkich tabel zostały zaktualizowane.
Ostatnia linia wyniku jest nieco myląca – statystyki dla wszystkich tabel nie zostały zaktualizowane, zaktualizowane zostały jedynie statystyki, które miały jeden lub więcej wierszy zmodyfikowanych. I znowu, wadą tego jest to, że być może wykorzystano zasoby, które nie musiały być. Jeśli statystyka ma zmodyfikowany tylko jeden wiersz, czy powinna zostać zaktualizowana? Nie. Jeśli ma zaktualizowanych 10 000 wierszy, czy powinien zostać zaktualizowany? Cóż, to zależy. Jeśli tabela ma tylko 5000 wierszy, to bezwzględnie; jeśli tabela ma 1 milion wierszy, to nie, ponieważ tylko jeden procent tabeli został zmodyfikowany.
Na wynos jest to, że jeśli używasz sp_updatestats
aby zaktualizować statystyki, najprawdopodobniej marnujesz zasoby, w tym procesor, we/wy i tempdb. Co więcej, aktualizacja każdej statystyki zajmuje trochę czasu, a jeśli masz wąskie okno konserwacji, prawdopodobnie masz inne zadania konserwacyjne, które można wykonać w tym czasie, zamiast niepotrzebnych aktualizacji. Wreszcie, prawdopodobnie nie zapewniasz żadnych korzyści związanych z wydajnością, aktualizując statystyki, gdy zmieniło się tak niewiele wierszy. Zmiana rozkładu jest prawdopodobnie nieistotna, jeśli zmodyfikowano tylko niewielki procent wierszy, więc histogram i wartości gęstości nie zmieniają się tak bardzo. Ponadto należy pamiętać, że aktualizacja statystyk unieważnia plany zapytań, które korzystają z tych statystyk. Po wykonaniu tych zapytań plany zostaną ponownie wygenerowane, a plan prawdopodobnie będzie dokładnie taki sam, jak wcześniej, ponieważ nie nastąpiła żadna znacząca zmiana w histogramie. Ponowna kompilacja planów zapytań wiąże się z pewnymi kosztami – nie zawsze jest to łatwe do zmierzenia, ale nie należy tego ignorować.
Lepszą metodą zarządzania statystykami — ponieważ trzeba zarządzać statystykami — jest zaimplementowanie zaplanowanego zadania, które aktualizuje się na podstawie wartości procentowej zmodyfikowanych wierszy. Możesz użyć powyższego zapytania, które odpytuje sys.sysindexes
, lub możesz użyć poniższego zapytania, które korzysta z nowego DMF dodanego w SQL Server 2008 R2 z dodatkiem SP2 i SQL Server 2012 z dodatkiem SP1:
SELECT [sch].[name] + '.' + [so].[name] AS [TableName] , [ss].[name] AS [Statistic], [sp].[last_updated] AS [StatsLastUpdated] , [sp].[rows] AS [RowsInTable] , [sp].[rows_sampled] AS [RowsSampled] , [sp].[modification_counter] AS [RowModifications] FROM [sys].[stats] [ss] JOIN [sys].[objects] [so] ON [ss].[object_id] = [so].[object_id] JOIN [sys].[schemas] [sch] ON [so].[schema_id] = [sch].[schema_id] OUTER APPLY [sys].[dm_db_stats_properties]([so].[object_id], [ss].[stats_id]) sp WHERE [so].[type] = 'U' AND [sp].[modification_counter] > 0 ORDER BY [sp].[last_updated] DESC;
Zdaj sobie sprawę, że różne tabele mogą mieć różne progi i będziesz musiał dostosować powyższe zapytanie dla swoich baz danych. W przypadku niektórych tabel poczekanie, aż 15% lub 20% wierszy zostanie zmodyfikowanych, może być w porządku. Ale w przypadku innych może być konieczna aktualizacja o 10% lub nawet 5%, w zależności od rzeczywistych wartości i ich pochylenia. Nie ma srebrnej kuli. Chociaż kochamy absoluty, rzadko występują one w SQL Server, a statystyki nie są wyjątkiem. Nadal chcesz pozostawić włączone statystyki automatycznej aktualizacji — jest to zabezpieczenie, które zadziała, jeśli coś przeoczysz, podobnie jak automatyczny wzrost plików bazy danych. Ale najlepiej jest znać swoje dane i wdrożyć metodologię, która pozwala aktualizować statystyki na podstawie procentu zmienionych wierszy.