Statystyki znikają po aktualizacji przyrostowej

21

Mamy dużą partycjonowaną bazę danych SQL Server wykorzystującą statystyki przyrostowe. Wszystkie indeksy są podzielone na partycje. Kiedy próbujemy odbudować partycję online według partycji, wszystkie statystyki znikają po odbudowaniu indeksu.

Poniżej znajduje się skrypt do replikacji problemu w SQL Server 2014 z bazą danych AdventureWorks2014.

--Example against AdventureWorks2014 Database

CREATE PARTITION FUNCTION TransactionRangePF1 (DATETIME)
AS RANGE RIGHT FOR VALUES 
(
   '20130501', '20130601', '20130701', '20130801', 
   '20130901', '20131001', '20131101', '20131201', 
   '20140101', '20140201', '20140301'
);
GO

CREATE PARTITION SCHEME TransactionsPS1 AS PARTITION TransactionRangePF1 TO 
(
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY]
);
GO

CREATE TABLE dbo.TransactionHistory 
(
  TransactionID        INT      NOT NULL, -- not bothering with IDENTITY here
  ProductID            INT      NOT NULL,
  ReferenceOrderID     INT      NOT NULL,
  ReferenceOrderLineID INT      NOT NULL DEFAULT (0),
  TransactionDate      DATETIME NOT NULL DEFAULT (GETDATE()),
  TransactionType      NCHAR(1) NOT NULL,
  Quantity             INT      NOT NULL,
  ActualCost           MONEY    NOT NULL,
  ModifiedDate         DATETIME NOT NULL DEFAULT (GETDATE()),
  CONSTRAINT CK_TransactionType 
    CHECK (UPPER(TransactionType) IN (N'W', N'S', N'P'))
) 
ON TransactionsPS1 (TransactionDate);


INSERT INTO dbo.TransactionHistory
SELECT * FROM Production.TransactionHistory
--  SELECT * FROM sys.partitions
--  WHERE object_id = OBJECT_ID('dbo.TransactionHistory');

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW, STATISTICS_INCREMENTAL=ON)  
  ON TransactionsPS1 (TransactionDate)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT 'Stats are avialable'  

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

PRINT 'After online index rebuild by partition stats are now gone'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Rebuild the stats with a rebuild for all paritions (this works)' 
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = ALL WITH (ONLINE = ON , DATA_COMPRESSION = ROW, 
  STATISTICS_INCREMENTAL = ON)

PRINT 'Stats are back'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Works correctly for an offline rebuild by partition'
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = OFF , DATA_COMPRESSION = ROW)

    --stats still there  
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT' stats are gone!!!!!!'

Jak pokazano, nie możemy odbudować indeksów według partycji online bez utraty wszystkich statystyk dla indeksu. Jest to dla nas poważny problem z utrzymaniem. Wygląda na to, że opcja przyrostowa statystyki musi być częścią składni przebudowy pojedynczego indeksu lub opcja online musi odpowiednio ją obsługiwać, tak jak robi to opcja offline.

Daj mi znać, jeśli coś mi umknie?

Aktualizacje:

O ile potrzebujemy statystyk przyrostowych: dzielimy się na wewnętrzny identyfikator klienta, a nie na datę. Kiedy więc zostaje wprowadzony nowy klient (duże obciążenie danych), możemy po prostu zaktualizować statystyki partycji i szybko uniknąć tworzenia brzydkich planów dla tego nowego klienta. Myślę, że napiszę to do Microsoft jako błąd i zobaczę, co mają do powiedzenia, i zastosuję rozwiązanie polegające na ponownym próbkowaniu statystyk dla tej partycji.

Połącz raport o błędzie:

Statystyki znikają po odbudowaniu indeksu online ze statystykami przyrostowymi

Aktualizacja: Microsoft potwierdził, że jest to błąd.

JasonR
źródło
1
Aktualizacja: Microsoft wysłał mi dziś rano e-maila, że ​​ten błąd zostanie naprawiony w następnej aktualizacji CU dla SQL 2014.
JasonR
czy wiesz, które jednostki naprawcze to naprawiły, lub jaka jest KB, że zgłosiły się w tym e-mailu? Próbuję zobaczyć, kiedy został naprawiony.
mbourgon
1
Całkiem pewny, że to numer błędu VSTS 8046729 KB Numer artykułu 3194959, który był częścią CU 9 dla SQL Server 2014 SP1. Link do KB jest tutaj .
JasonR
Tak, tak to wygląda - i zostało właśnie naprawione w 2016SP1 w zeszłym miesiącu. Wielkie dzięki!
mbourgon
Korekta: właśnie naprawiona w 2016 SP1 CU2. Zdarza się to w 2016 SP1 CU1.
mbourgon

Odpowiedzi:

17

Nie wiem, czy jest to błąd, per se , ale to z pewnością interesujące zjawisko. Przebudowy partycji online są nowe w SQL Server 2014, więc mogą być pewne elementy wewnętrzne do posortowania z tym.

Oto moje najlepsze wytłumaczenie dla ciebie. Przyrostowe statystyki absolutnie wymagają, aby wszystkie partycje były próbkowane w tym samym tempie, aby podczas łączenia stron statystyk silnik miał pewność, że rozkład próbkowania jest porównywalny. REBUILDkoniecznie próbkuje dane ze 100% częstotliwością próbkowania. Nie ma gwarancji, że 100% częstotliwości próbkowania na partycji 9 zawsze będzie dokładną częstotliwością próbkowania dla pozostałych partycji. Z tego powodu wygląda na to, że silnik nie może scalić próbek i kończy się pustą kroplą statystyk. Jednak obiekt statystyk wciąż tam jest:

select 
    check_time = sysdatetime(),                         
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    stats_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
    and s.name = i.name
outer apply sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
where t.name = 'TransactionHistory' and sh.name = 'dbo'

Kroplę można wypełnić na wiele sposobów:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

lub

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE ON PARTITIONS (9);

lub możesz poczekać na aktualizację AutoStats przy pierwszej kompilacji planu zapytań przy użyciu tego obiektu:

-- look at my creative query
select * 
from dbo.TransactionHistory
where TransactionDate = '20140101';

Powiedziawszy to wszystko, ten oświecający post Erin Stellato podkreśla to, co zostało uznane za poważny brak statystyk przyrostowych. Dane na poziomie partycji nie są wykorzystywane przez optymalizator do generowania planu zapytań, co zmniejsza przypuszczalną korzyść statystyki przyrostowej. Jaka jest zatem obecna korzyść ze statystyki przyrostowej? Twierdzę, że ich podstawową użytecznością jest możliwość bardziej spójnego próbkowania dużych tabel z wyższą częstotliwością niż w przypadku tradycyjnych statystyk.

Korzystając z Twojego przykładu, oto jak to wygląda:

set statistics time on;

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

--SQL Server Execution Times:
--  CPU time = 94 ms,  elapsed time = 131 ms.


update statistics dbo.TransactionHistory(IDX_ProductId)
with resample on partitions(2);

 --SQL Server Execution Times:
 --  CPU time = 0 ms,  elapsed time = 5 ms.

drop index IDX_ProductId On dbo.TransactionHistory;

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW)  
  ON [PRIMARY]

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

 --SQL Server Execution Times:
 --  CPU time = 76 ms,  elapsed time = 66 ms.

Aktualizacja statystyk fullscan dotycząca statystyki przyrostowej kosztuje 131 ms. Aktualizacja statystyki fullscan statystyki nieprzystosowanej do partycji kosztuje 66 ms. Nieprzystosowana statystyka jest wolniejsza, najprawdopodobniej z powodu ogólnych kosztów związanych z łączeniem poszczególnych stron statystyki z powrotem do głównego histogramu. Jednak za pomocą obiektu statystycznego wyrównanego do partycji możemy zaktualizować jedną partycję i scalić ją z powrotem w główny obiekt blob histogramu w 5 ms. W tym momencie administrator z przyrostową statystyką musi podjąć decyzję. Mogą skrócić całkowity czas utrzymywania statystyk, aktualizując tylko partycje, lub tradycyjnie musiałyby być aktualizowane, lub mogą eksperymentować z wyższymi częstotliwościami próbkowania, aby potencjalnie uzyskać więcej wierszy próbkowanych w tym samym okresie czasu, co poprzednie okresy konserwacji. Ten pierwszy pozwala odetchnąć w oknie konserwacji, ten drugi może przesunąć statystyki na bardzo dużym stole do miejsca, w którym zapytania uzyskują lepsze plany w oparciu o dokładniejsze statystyki. Nie stanowi to gwarancji, a przebieg może się różnić.

Czytelnik widzi, że 66 ms nie jest bolesnym czasem aktualizacji statystyk w tej tabeli, więc próbowałem skonfigurować test na zestawie danych wymiany stosów. W ostatnim zrzucie, który pobrałem, jest 6 418 608 postów (z wyłączeniem postów StackOverflow i wszystkich postów z 2012 r. - z mojej strony błąd danych).

Dane podzieliłem na partycje, [CreationDate]ponieważ ... demo.

Oto niektóre czasy dla niektórych dość standardowych scenariuszy (100% - przebudowa indeksu, domyślnie - automatyczna aktualizacja statystyk lub UPDATE STATISTICSbez określonej częstotliwości próbkowania:

  • Twórz statystyki przyrostowe za pomocą Fullscan: czas procesora = 23500 ms, czas, który upłynął = 22521 ms.
  • Twórz statystyki przyrostowe z Fullscan: czas procesora = 20406 ms, czas, który upłynął = 15413 ms.
  • Zaktualizuj statystykę nieinkrementalną o domyślną częstotliwość próbkowania: czas procesora = 406 ms, czas, który upłynął = 408 ms.
  • Zaktualizuj statystykę przyrostową z domyślną częstotliwością próbkowania: czas procesora = 453 ms, czas, który upłynął = 507 ms.

Powiedzmy, że jesteśmy bardziej wyrafinowani niż te domyślne scenariusze i zdecydowaliśmy, że 10% częstotliwość próbkowania jest minimalną częstotliwością, która powinna zapewnić nam potrzebne plany, przy jednoczesnym utrzymaniu rozsądnego czasu konserwacji.

  • Zaktualizuj statystykę nieinkrementalną o próbkę 10 procent: czas procesora = 2344 ms, czas, który upłynął = 2441 ms.
  • Zaktualizuj statystykę przyrostową o próbkę 10 procent: czas procesora = 2344 ms, czas, który upłynął = 2388 ms.

Jak dotąd nie ma wyraźnych korzyści z posiadania przyrostowej statystyki. Jeśli jednak wykorzystamy nieudokumentowane sys.dm_db_stats_properties_internal() DMV (poniżej), możesz uzyskać wgląd w to, które partycje chcesz zaktualizować. Załóżmy, że wprowadziliśmy zmiany w danych na partycji 3 i chcemy mieć pewność, że statystyki będą aktualne dla zapytań przychodzących. Oto nasze opcje:

  • Zaktualizuj domyślnie jako niekrementalne (także domyślne zachowanie aktualizacji Auto-Stats): 408 ms.
  • Zaktualizuj wartość niekrementalną przy 10%: 2441 ms.
  • Zaktualizuj statystyki przyrostowe, partycja 3 z ponownym próbkowaniem (10% - nasza zdefiniowana częstotliwość próbkowania): czas procesora = 63 ms, czas, który upłynął = 63 ms.

Oto, gdzie musimy podjąć decyzję. Czy przyjmujemy zwycięstwo 63 ms. aktualizacja statystyk oparta na partycjach, czy też zwiększamy częstotliwość próbkowania jeszcze wyżej? Powiedzmy, że jesteśmy gotowi przyjąć początkowe trafienie próbkowania na poziomie 50% na podstawie przyrostowej statystyki:

  • Aktualizuj statystyki przyrostowe o 50%: upływ czasu = 16840 ms.
  • Aktualizuj statystyki przyrostowe, partycja 3 z ponownym próbkowaniem (50% - nasz nowy czas aktualizacji): czas, który upłynął = 295 ms.

Jesteśmy w stanie próbkować o wiele więcej danych, być może konfigurując optymalizator, aby lepiej odgadywać nasze dane (nawet jeśli nie używa to jeszcze statystyk na poziomie partycji) i jesteśmy w stanie zrobić to szybciej, gdy już mamy statystyki przyrostowe.

Jeszcze jedna fajna rzecz do wymyślenia. Co z aktualizacjami statystyk synchronicznych? Czy 50% częstotliwość próbkowania jest zachowana nawet po uruchomieniu autostatów?

Usunąłem dane z partycji 3 i uruchomiłem zapytanie w CreationDate i sprawdziłem, a następnie sprawdziłem stawki za pomocą tego samego zapytania poniżej. Częstotliwość próbkowania 50% została zachowana.

Krótko mówiąc: statystyki przyrostowe mogą być użytecznym narzędziem przy odpowiedniej ilości przemyślenia i początkowej konfiguracji. Musisz jednak znać problem, który próbujesz rozwiązać, a następnie odpowiednio go rozwiązać. Jeśli otrzymujesz złych oszacowań liczności, to może być w stanie uzyskać lepsze plany strategiczne z częstotliwością próbkowania i niektórych zainwestowanego interwencji. Otrzymujesz jednak tylko niewielką część korzyści, ponieważ użyty histogram to pojedyncza, scalona strona statystyk, a nie informacje na poziomie partycji. Jeśli odczuwasz ból w oknie konserwacji, być może pomocne mogą być statystyki przyrostowe, ale prawdopodobnie będzie to wymagało skonfigurowania interwencji konserwacyjnej w trybie szybkiego dotyku. Bez względu,:

  • Statystyki tworzone za pomocą indeksów, które nie są wyrównane do partycji z tabelą podstawową.
  • Statystyki tworzone na wtórnych bazach danych AlwaysOn.
  • Statystyki tworzone w bazach danych tylko do odczytu.
  • Statystyki tworzone na filtrowanych indeksach.
  • Statystyki tworzone na podstawie widoków.
  • Statystyki tworzone na wewnętrznych tabelach.
  • Statystyki tworzone za pomocą indeksów przestrzennych lub indeksów XML.

Mam nadzieję że to pomoże

select 
    sysdatetime(),                          
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    leading_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    parition_number = isnull(sp.partition_number,1),
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter = coalesce(sp.modification_counter, n1.modification_counter) 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
        and s.name = i.name
cross apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) sp
outer apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) n1
where n1.node_id = 1
    and (
            (is_incremental = 0)
               or
            (is_incremental = 1 and sp.partition_number is not null)
         )
    and t.name = 'Posts'
    and s.name like 'st_posts%'
order by s.stats_id,isnull(sp.partition_number,1)
swasheck
źródło