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:
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:
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.
źródło
Odpowiedzi:
Problem występuje w tej części kodu:
or
w warunkach dołączania jest zawsze podejrzane. Jedną z sugestii jest podzielenie tego na dwa połączenia:Następnie musisz zmodyfikować resztę zapytania, aby to obsłużyć. . .
coalesce(l1.sku_id, l2.sku_id)
na przykład wselect
klauzuli.źródło
JOIN
DoCROSS APPLY
zIN
przełączeniemEXISTS
wAPPLY
„sWHERE
klauzuli.ON l.category_id = ISNULL(c5.category_id, c4.category_id
.coalesce()
popycha optymalizator we właściwym kierunku.Jak wspomniał inny użytkownik, to przyłączenie jest prawdopodobnie przyczyną:
Oprócz podziału na wiele złączeń, możesz także spróbować
CROSS APPLY
Z linku MSDN powyżej:
Zasadniczo
APPLY
jest 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 APPLY
nie 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 APPLY
będzie działać jakLEFT JOIN
Ponadto prosimy o zapoznanie się z moim wyborem do użytku
EXISTS
zamiastIN
. WykonującIN
podzapytanie, pamiętaj, że zwróci cały zestaw wyników, nawet po znalezieniu wartości. DziękiEXISTS
, chociaż, to przestanie Podzapytanie moment znajdzie dopasowanie.źródło
AND x.cat = c4.cat OR x.cat = c5.cat
przezx.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.