Jak zoptymalizować zapytanie, które działa wolno w zagnieżdżonych pętlach (sprzężenie wewnętrzne)


39

TL; DR

Ponieważ to pytanie wciąż się wyświetla, podsumuję je tutaj, aby nowi przybysze nie musieli cierpieć historii:

JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2)    -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa

Zdaję sobie sprawę, że to nie może być problem każdego, ale podkreślenie wrażliwości klauzul ON może pomóc ci spojrzeć we właściwym kierunku. W każdym razie oryginalny tekst jest dostępny dla przyszłych antropologów:

Oryginalny tekst

Rozważ następujące proste zapytanie (tylko 3 tabele)

    SELECT

        l.sku_id AS ProductId,
        l.is_primary AS IsPrimary,
        v1.category_name AS Category1,
        v2.category_name AS Category2,
        v3.category_name AS Category3,
        v4.category_name AS Category4,
        v5.category_name AS Category5

    FROM category c4
    JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'

    JOIN category c3 ON c3.category_id = c4.parent_category_id
    JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'

    JOIN category c2 ON c2.category_id = c3.category_id
    JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'

    JOIN category c1 ON c1.category_id = c2.parent_category_id
    JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'

    LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
    LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang

    JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
    (
        l.category_id = c4.category_id OR
        l.category_id = c5.category_id
    )

    WHERE c4.[level] = 4 AND c4.version_id = 5

Jest to dość proste zapytanie, jedyną mylącą częścią jest ostatnie połączenie kategorii, jest tak, ponieważ poziom kategorii 5 może, ale nie musi istnieć. Na końcu zapytania szukam informacji o kategorii według identyfikatora produktu (SKU ID) i tam właśnie pojawia się bardzo duża tabela link_ kategorii. Wreszcie tabela #Ids jest tylko tabelą tymczasową zawierającą 10 000 ID.

Po wykonaniu otrzymuję następujący rzeczywisty plan wykonania:

Rzeczywisty plan wykonania

Jak widać, prawie 90% czasu spędza się w zagnieżdżonych pętlach (sprzężenie wewnętrzne). Oto dodatkowe informacje na temat tych zagnieżdżonych pętli:

Zagnieżdżone pętle (sprzężenie wewnętrzne)

Zauważ, że nazwy tabel nie pasują dokładnie, ponieważ zredagowałem nazwy tabel zapytań dla czytelności, ale dość łatwo można je dopasować (ads_alt_category = kategoria). Czy istnieje sposób zoptymalizowania tego zapytania? Zauważ też, że w produkcji tabela temp. #Ids nie istnieje, jest to parametr wyceniony w tabeli o tych samych 10 000 ID przekazanych do procedury składowanej.

Dodatkowe informacje:

  • indeksy kategorii na id_kategorii i id_kategorii nadrzędnej
  • index_voc category_id, kod_języka
  • indeks_kategorii na sku_id, id_kategorii

Edytuj (rozwiązany)

Jak wskazano w zaakceptowanej odpowiedzi, problemem była klauzula OR w JOIN category_link. Jednak kod sugerowany w zaakceptowanej odpowiedzi jest bardzo wolny, wolniejszy nawet niż kod oryginalny. O wiele szybszym i czystszym rozwiązaniem jest po prostu zastąpienie obecnego warunku JOIN następującym:

JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)

Ta drobna poprawka jest najszybszym rozwiązaniem, przetestowanym pod kątem podwójnego łączenia z zaakceptowanej odpowiedzi, a także przetestowanym pod kątem ZASTOSOWANIA KRZYŻOWEGO, jak sugeruje valverij.


Musimy zobaczyć resztę planu zapytań.
RBarryYoung 17.04.13

Tylko uwaga: przy tak wielu sprzężeniach zależnych prawdopodobieństwo oszacowania liczności staje się prawdopodobne. Najczęściej wydajność zapytania jest zaburzona przez niedoszacowanie liczności.
usr

Czy plan wykonania zawiera sugestie dotyczące indeksów? Nie zapominaj również, że możesz ustawić klucze podstawowe i indeksy w tabelach tymczasowych (więcej informacji tutaj )

@rbarry Jeśli po wypróbowaniu obecnych rozwiązań nic nie dostanę, poprawię pytanie

1
Co powiesz na powielenie zapytania z

Odpowiedzi:


17

Problem występuje w tej części kodu:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

orw warunkach dołączania jest zawsze podejrzane. Jedną z sugestii jest podzielenie tego na dwa połączenia:

JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id

Następnie musisz zmodyfikować resztę zapytania, aby to obsłużyć. . . coalesce(l1.sku_id, l2.sku_id)na przykład w selectklauzuli.


Z kwoty filtrowanie robione na ten konkretny przyłączyć, ja również przetestować zmieniając JOINDo CROSS APPLYz INprzełączeniem EXISTSw APPLY„s WHEREklauzuli.

Dzięki Gordon, przetestuję tę pierwszą rzecz rano. @Valverij, nie jestem zaznajomiony z aplikacją krzyżową, czy mógłbyś bardziej opisać swoje rozwiązanie, może we właściwej odpowiedzi, więc mogę głosować, jeśli okaże się to najszybszym scenariuszem?

3
Przyjmuję tę odpowiedź, ponieważ była to pierwsza odpowiedź, która wskazała mi problem. Sugerowane rozwiązanie jest jednak bardzo wolne, wolniejsze nawet niż oryginalny kod. Jednak wiedząc, że problem stanowi klauzula OR, wystarczy po prostu zastąpić ją klauzulą ON l.category_id = ISNULL(c5.category_id, c4.category_id.
Luis Ferrao,

1
@LuisFerrao. . . Dziękujemy za dodatkowe informacje. Warto wiedzieć, że coalesce()popycha optymalizator we właściwym kierunku.
Gordon Linoff,

9

Jak wspomniał inny użytkownik, to przyłączenie jest prawdopodobnie przyczyną:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

Oprócz podziału na wiele złączeń, możesz także spróbować CROSS APPLY

CROSS APPLY (
    SELECT [some column(s)]
    FROM category_link x
    WHERE EXISTS(SELECT value FROM #Ids WHERE value = x.sku_id)
    AND (x.category_id = c4.category_id OR x.category_id = c5.category_id)        
) l

Z linku MSDN powyżej:

Funkcja wyceniana w tabeli działa jako prawe wejście, a wyrażenie zewnętrznej tabeli działa jak lewe wejście. Prawe dane wejściowe są oceniane dla każdego wiersza od lewego wejścia, a wyprodukowane wiersze są łączone w celu uzyskania końcowego wyniku .

Zasadniczo APPLYjest jak podkwerenda, która najpierw odfiltrowuje rekordy po prawej, a następnie stosuje je do pozostałej części zapytania.

Ten artykuł bardzo dobrze wyjaśnia, co to jest i kiedy go używać: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/

Należy jednak pamiętać, że CROSS APPLYnie zawsze działa szybciej niż INNER JOIN. W wielu sytuacjach prawdopodobnie będzie tak samo. Jednak w rzadkich przypadkach widziałem, że jest wolniejszy (znowu wszystko zależy od struktury tabeli i samego zapytania).

Zgodnie z ogólną zasadą, jeśli dołączam do stołu ze zbyt dużą liczbą instrukcji warunkowych, to skłaniam się ku APPLY

Również fajna uwaga: OUTER APPLYbędzie działać jakLEFT JOIN

Ponadto prosimy o zapoznanie się z moim wyborem do użytku EXISTSzamiast IN. Wykonując INpodzapytanie, pamiętaj, że zwróci cały zestaw wyników, nawet po znalezieniu wartości. Dzięki EXISTS, chociaż, to przestanie Podzapytanie moment znajdzie dopasowanie.


Dokładnie przetestowałem to rozwiązanie. Podczas pisania jest dość powolny, ale zapomniałeś zastosować się do porady, od której zacząłeś wiadomość. Wymiana AND x.cat = c4.cat OR x.cat = c5.catprzez x.cat = ISNULL(c5.cat, c4.cat)i pozbycie się klauzula wykonany ten drugi najszybsze rozwiązanie i godna upvote, ponieważ jest to dość pouczające.
Luis Ferrao,

Dzięki. Linia IN właściwie nie powinna była tam być (nie mogłem zdecydować się na użycie IN lub pozostanie przy OR), usunę ją.
valverij
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.