W jaki sposób można poprawić oszacowania wierszy, aby zmniejszyć ryzyko wycieków do tempdb

11

Zauważam, że gdy dochodzi do rozlania zdarzeń tempdb (powodujących powolne zapytania), to często szacunki wierszy są dalekie od konkretnego łączenia. Widziałem zdarzenia rozlewania występujące w połączeniach scalania i mieszania, które często zwiększają czas działania 3x do 10x. To pytanie dotyczy sposobu poprawy oszacowań wierszy przy założeniu, że zmniejszy to ryzyko wystąpienia wycieków.

Rzeczywista liczba rzędów 40 tys.

W przypadku tego zapytania plan pokazuje złe oszacowanie wiersza (11,3 wiersza):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

W przypadku tego zapytania plan pokazuje dobre oszacowanie wiersza (56 tys. Wierszy):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

Czy można dodać statystyki lub wskazówki, aby poprawić szacunki wierszy dla pierwszego przypadku? Próbowałem dodać statystyki z określonymi wartościami filtrów (właściwość = 2840), ale albo nie udało się uzyskać poprawnej kombinacji, albo być może jest ona ignorowana, ponieważ ObjectId jest nieznany w czasie kompilacji i może wybierać średnią dla wszystkich ObjectIds.

Czy jest jakiś tryb, w którym najpierw wykonałby zapytanie z sondy, a następnie użył go do ustalenia szacunków wiersza, czy też musi latać na ślepo?

Ta konkretna właściwość ma wiele wartości (40k) na kilku obiektach i zero na zdecydowanej większości. Byłbym zadowolony z podpowiedzi, w której można określić maksymalną oczekiwaną liczbę wierszy dla danego sprzężenia. Jest to ogólnie problem nawiedzający, ponieważ niektóre parametry mogą być określane dynamicznie jako część złączenia lub byłyby lepiej umieszczone w widoku (brak obsługi zmiennych).

Czy są jakieś parametry, które można dostosować, aby zminimalizować ryzyko wycieków do tempdb (np. Minimalna pamięć na zapytanie)? Solidny plan nie miał wpływu na oszacowanie.

Edytuj 2013.11.06 : Odpowiedź na komentarze i dodatkowe informacje:

Oto obrazy planu zapytania. Ostrzeżenia dotyczą predykatu liczności / poszukiwania za pomocą funkcji convert ():

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Według komentarza @Aarona Bertranda próbowałem zastąpić metodę convert () jako test:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

wprowadź opis zdjęcia tutaj

Jako dziwny, ale udany punkt zainteresowania, pozwolił również na zwarcie wyszukiwania:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

wprowadź opis zdjęcia tutaj

Oba z nich zawierają poprawne wyszukiwanie klucza, ale tylko te pierwsze zawierają „wynik” ObjectId. Wydaje mi się, że to wskazuje, że drugim jest zwarcie?

Czy ktoś może zweryfikować, czy kiedykolwiek wykonywane są sondy jednorzędowe, aby pomóc w oszacowaniu rzędu? Wydaje się niewłaściwe ograniczanie optymalizacji do szacunków histogramu tylko wtedy, gdy jednorzędowe wyszukiwanie PK może znacznie poprawić dokładność wyszukiwania w histogramie (szczególnie jeśli istnieje potencjał rozlania lub historia). Gdy w prawdziwym zapytaniu występuje 10 takich podłączy, idealnie byłyby one realizowane równolegle.

Na marginesie, ponieważ sql_variant przechowuje swój typ podstawowy (SQL_VARIANT_PROPERTY = BaseType) w samym polu, oczekiwałbym, że funkcja convert () będzie prawie bezkosztowa, pod warunkiem, że jest „bezpośrednio” konwertowalna (np. Nie ciąg znaków na dziesiętne, ale raczej int int, a może int to bigint). Ponieważ nie jest to znane w czasie kompilacji, ale może być znane użytkownikowi, być może funkcja „AssumeType (typ, ...)” dla zmiennych_sql pozwoliłaby na ich bardziej przejrzyste traktowanie.

Crokusek
źródło
1
Pierwsze przypuszczenie byłoby takie, że konwersja do biginta odrzuca twoje oszacowania (plan zapytań wyświetli ostrzeżenie o tym w SQL Server 2012), ale z drugiej strony twoje zapytanie podrzędne nigdy nie zwróci niczego innego niż 0 lub 1 wierszy za udane zapytanie. Byłoby interesujące zobaczyć twoje plany zapytań. Być może jako link do wersji XML.
Mikael Eriksson
2
Co zyskujesz dzięki wbudowaniu podzapytania? Sugerowałbym, że wyciągnięcie go osobno jest ogólnie bardziej przejrzyste, a ponieważ prowadzi to do lepszych oszacowań, dlaczego po prostu nie zastosować tej metody?
Aaron Bertrand
2
Jaka wersja SQL Server? Czy możesz podać tabelę i indeks DDL oraz statystyki obiektów blob (jedno- i wielokolumnowych) dla tabel, abyśmy mogli zobaczyć szczegóły problemu? Dzielenie zapytania za pomocą declare @a bigint = tego, co zrobiłeś, wydaje mi się naturalnym rozwiązaniem, dlaczego jest to niedopuszczalne?
Paul White 9
2
Myślę, że twój projekt jest (bardzo uproszczony) projektem EAV, który zmusza cię do użycia CONVERT()w kolumnach, a następnie do ich połączenia. To z pewnością nie jest wydajne w większości przypadków. W tym konkretnym przypadku należy przekonwertować tylko jedną wartość, więc prawdopodobnie nie jest to problem, ale jakie indeksy masz na stole? Projekty EAV zwykle działają dobrze, tylko przy odpowiednim indeksowaniu (co oznacza wiele indeksów w zwykle wąskich tabelach).
ypercubeᵀᴹ
@Paul White, jeśli chodzi o dzielenie ... to jest dobre rozwiązanie dla tej sprawy. Ale w przypadku bardziej ogólnych / złożonych najczęściej nie chcę rezygnować z równoległości i czytelności. Powiedzmy, że mam 10 takich zapytań (niektóre są bardziej złożone) w zapytaniu, ale tylko 5 musi być „dojrzałych”, zanim reszta zapytania będzie mogła się rozpocząć - chciałbym uniknąć konieczności zorientowania się, które są 5.
crokusek

Odpowiedzi:

7

Nie będę komentował wycieków, tempdb ani podpowiedzi, ponieważ zapytanie wydaje się dość proste, aby wymagało tyle uwagi. Myślę, że optymalizator SQL-Server dobrze sobie poradzi, jeśli istnieją indeksy odpowiednie dla zapytania.

Podział na dwa zapytania jest dobry, ponieważ pokazuje, jakie indeksy będą przydatne. Pierwsza część:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

potrzebuje indeksu (PropertyId, ObjectId, Sequence)uwzględniającego Value. Sprawiłbym, UNIQUEżeby było bezpiecznie. Zapytanie i tak wygenerowałoby błąd podczas działania, gdyby zwrócono więcej niż jeden wiersz, więc dobrze jest wcześniej upewnić się, że tak się nie stanie, dzięki unikalnemu indeksowi:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

Druga część zapytania:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

potrzebuje indeksu (PropertyId, ObjectId)obejmującego Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Jeśli wydajność nie ulegnie poprawie lub indeksy te nie zostaną użyte lub nadal będą występować różnice w pojawiających się szacunkach wierszy, konieczne będzie dalsze przyjrzenie się temu zapytaniu.

W takim przypadku konwersje (wymagane z projektu EAV i przechowywania różnych typów danych w tych samych kolumnach) są prawdopodobną przyczyną, a twoje rozwiązanie podziału (jak komentarz @Aron Bertrand i @Paul White) wydaje się naturalne i droga do przebycia. Przeprojektowanie, aby mieć różne typy danych w odpowiednich kolumnach, może być innym.

ypercubeᵀᴹ
źródło
Tabele zawierały indeksy - powinienem to stwierdzić w pytaniu. Przykładem jest tak naprawdę łącznik podrzędny zasilający większe zapytanie, i dlatego jest tyle zamieszania w związku z wyciekami tempdb.
crokusek
5

Jako częściowa odpowiedź na jednoznaczne pytanie dotyczące poprawy statystyk ...

Zauważ, że szacunki rzędu nawet dla osobnego zepsutego przypadku są nadal wyłączone o 10X (4k vs oczekiwane 40k).

Histogram statystyczny prawdopodobnie był zbyt cienki dla tej właściwości, ponieważ jest to długa (pionowa) tabela wierszy o wielkości 3,5 mln, a ta konkretna właściwość jest bardzo rzadka.

Utwórz dodatkowe statystyki (nieco zbędne w przypadku statystyki IX) dla rzadkiej właściwości:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Pierwotni:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Po usunięciu konwersji () (właściwej):

wprowadź opis zdjęcia tutaj

Po usunięciu konwersji () (krótki obwód):

wprowadź opis zdjęcia tutaj

Prawdopodobnie jeszcze ~ 2X prawdopodobne, ponieważ> 99,9% obiektów w ogóle nie ma zdefiniowanej właściwości 2840. W rzeczywistości tylko w tym przypadku testowym właściwość istnieje tylko na 1 z 200 000 różnych obiektów tabeli wierszy 3,5 mln. To niesamowite, że jest tak blisko. Dostosowując filtr do mniejszej liczby ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, bez zmian ... Kopia zapasowa dodała „z pełnym skanowaniem” na końcu statystyk (być może dlatego poprzednie dwa nie działały) i tak:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

wprowadź opis zdjęcia tutaj

Tak Tak więc w wysoce pionowej tabeli z szeroko zakrywającym IX dodanie dodatkowej filtrowanej statystyki wydaje się być dużym ulepszeniem (szczególnie w przypadku rzadkich, ale bardzo różnorodnych kombinacji klawiszy).

Crokusek
źródło
Link do niektórych problematycznych problemów ze statystykami wielokolumnowymi.
crokusek