Plan został skompilowany na wystąpieniu SQL Server 2008 R2 RTM (kompilacja 10.50.1600). Należy zainstalować dodatek Service Pack 3 (kompilacja 10.50.6000), a następnie najnowsze łaty, aby doprowadzić go do (bieżącej) ostatniej kompilacji 10.50.6542. Jest to ważne z wielu powodów, w tym z bezpieczeństwa, poprawek błędów i nowych funkcji.
Optymalizacja osadzania parametrów
W związku z niniejszym pytaniem program SQL Server 2008 R2 RTM nie obsługiwał optymalizacji osadzania parametrów (PEO) OPTION (RECOMPILE)
. W tej chwili ponosisz koszty ponownej kompilacji, nie zdając sobie sprawy z jednej z głównych korzyści.
Gdy PEO jest dostępny, SQL Server może wykorzystywać dosłowne wartości przechowywane w zmiennych lokalnych i parametrach bezpośrednio w planie zapytań. Może to prowadzić do dramatycznych uproszczeń i wzrostu wydajności. Więcej informacji na ten temat znajduje się w moim artykule Parch Sniffing, Osadzanie i Opcje RECOMPILE .
Hash, sortuj i wymieniaj wycieki
Są one wyświetlane tylko w planach wykonania, gdy zapytanie zostało skompilowane na SQL Server 2012 lub nowszym. We wcześniejszych wersjach musieliśmy monitorować wycieki, gdy zapytanie było wykonywane przy użyciu Profiler lub Extended Events. Wycieki zawsze powodują fizyczne we / wy do (i od) stałej pamięci masowej tempdb , co może mieć ważne konsekwencje dla wydajności, szczególnie jeśli wyciek jest duży lub ścieżka we / wy jest pod presją.
W twoim planie wykonania są dwa operatory dopasowania mieszania (agregacji). Pamięć zarezerwowana dla tabeli skrótów jest oparta na szacunkach dla wierszy wyjściowych (innymi słowy, jest proporcjonalna do liczby grup znalezionych w czasie wykonywania). Przydzielona pamięć jest ustalana tuż przed rozpoczęciem wykonywania i nie może rosnąć podczas wykonywania, niezależnie od ilości wolnej pamięci, jaką ma instancja. W dostarczonym planie oba operatory dopasowania mieszania (agregujące) generują więcej wierszy niż oczekiwany optymalizator, więc może wystąpić wyciek do tempdb w czasie wykonywania.
W planie jest także operator dopasowania mieszania (przyłączenia wewnętrznego). Pamięć zarezerwowana dla tabeli skrótów jest oparta na szacunkach dla wierszy wejściowych po stronie sondy . Dane wejściowe sondy szacują 847,399 wierszy, ale w czasie wykonywania napotkano 1 223 636. Nadmiar ten może również powodować wyciek mieszania.
Zbędne agregaty
Dopasowanie mieszania (agregacja) w węźle 8 wykonuje operację grupowania (Assortment_Id, CustomAttrID)
, ale wiersze wejściowe są równe wierszom wyjściowym:
Sugeruje to, że kombinacja kolumn jest kluczem (więc grupowanie jest semantycznie niepotrzebne). Koszt wykonania redundantnej agregacji jest zwiększony przez konieczność dwukrotnego przekazania 1,4 miliona wierszy przez wymiany partycjonowania mieszającego (operatory równoległości po obu stronach).
Biorąc pod uwagę, że zaangażowane kolumny pochodzą z różnych tabel, przekazanie tej informacji o unikatowości do optymalizatora jest trudniejsze niż zwykle, aby uniknąć zbędnej operacji grupowania i niepotrzebnych wymian.
Niewystarczająca dystrybucja wątków
Jak zauważono w odpowiedzi Joe Obbisha , wymiana w węźle 14 używa partycjonowania mieszającego do rozdzielania wierszy między wątkami. Niestety niewielka liczba wierszy i dostępnych harmonogramów oznacza, że wszystkie trzy wiersze kończą się na jednym wątku. Pozornie równoległy plan przebiega szeregowo (z równoległym napowietrznym) aż do wymiany w węźle 9.
Możesz rozwiązać ten problem (w celu uzyskania podziału na rundy lub podziału na partycje), eliminując Odrębne sortowanie w węźle 13. Najprostszym sposobem na to jest utworzenie klastrowego klucza podstawowego na #temp
tabeli i wykonanie odrębnej operacji podczas ładowania tabeli:
CREATE TABLE #Temp
(
id integer NOT NULL PRIMARY KEY CLUSTERED
);
INSERT #Temp
(
id
)
SELECT DISTINCT
CAV.id
FROM @customAttrValIds AS CAV
WHERE
CAV.id IS NOT NULL;
Tymczasowe buforowanie statystyk tabeli
Pomimo użycia OPTION (RECOMPILE)
SQL Server nadal może buforować tymczasowy obiekt tabeli i powiązane statystyki między wywołaniami procedur. Jest to ogólnie pożądana optymalizacja wydajności, ale jeśli tymczasowa tabela jest zapełniona podobną ilością danych przy sąsiednich wywołaniach procedur, ponownie skompilowany plan może być oparty na niepoprawnych statystykach (buforowanych z poprzedniego wykonania). Jest to szczegółowo opisane w moich artykułach, Tabele tymczasowe w procedurach przechowywanych i Objaśnienie buforowania tabel tymczasowych .
Aby tego uniknąć, należy używać OPTION (RECOMPILE)
razem z jawnym UPDATE STATISTICS #TempTable
po zapełnieniu tabeli tymczasowej i przed odwołaniem do niej w zapytaniu.
Zapytanie przepisz
W tej części założono, że zmiany w tworzeniu #Temp
tabeli zostały już wprowadzone.
Biorąc pod uwagę koszty możliwych wycieków skrótu i zbędnego agregatu (i otaczających go giełd), opłaca się zmaterializować zestaw w węźle 10:
CREATE TABLE #Temp2
(
CustomAttrID integer NOT NULL,
Assortment_Id integer NOT NULL,
);
INSERT #Temp2
(
Assortment_Id,
CustomAttrID
)
SELECT
ACAV.Assortment_Id,
CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
ON T.id = ACAV.CustomAttributeValue_Id;
ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);
PRIMARY KEY
Dodaje się w oddzielnym etapie, aby zapewnić budowanie indeksu ma dokładnych informacji liczności, oraz w celu uniknięcia tymczasowe statystyki tabel buforowanie problemu.
Ta materializacja najprawdopodobniej wystąpi w pamięci (unikając tempdb I / O), jeśli instancja ma wystarczającą ilość dostępnej pamięci. Jest to jeszcze bardziej prawdopodobne po uaktualnieniu do SQL Server 2012 (SP1 CU10 / SP2 CU1 lub nowszy), który poprawił zachowanie Eager Write .
Ta akcja dostarcza optymalizatorowi dokładnych informacji o liczności zbioru pośredniego, pozwala mu tworzyć statystyki i pozwala nam zadeklarować (Assortment_Id, CustomAttrID)
jako klucz.
Plan dla populacji #Temp2
powinien wyglądać następująco (zwróć uwagę na skanowanie indeksu klastrowego #Temp
, brak sortowania odrębnego, a wymiana wykorzystuje teraz partycjonowanie rzędów w trybie round-robin):
Po udostępnieniu tego zestawu końcowe zapytanie staje się:
SELECT
A.Id,
A.AssortmentId
FROM
(
SELECT
T.Assortment_Id
FROM #Temp2 AS T
GROUP BY
T.Assortment_Id
HAVING
COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
ON A.Id = DT.Assortment_Id
WHERE
A.AssortmentType = @asType
OPTION (RECOMPILE);
Możemy ręcznie przepisać COUNT_BIG(DISTINCT...
jako prosty COUNT_BIG(*)
, ale dzięki nowym kluczowym informacjom optymalizator robi to za nas:
Ostateczny plan może wykorzystywać sprzężenie pętli / mieszania / scalania w zależności od informacji statystycznych o danych, do których nie mam dostępu. Jeszcze jedna mała uwaga: założyłem, że CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);
istnieje taki indeks .
W każdym razie ważną rzeczą w ostatecznych planach jest to, że oszacowania powinny być znacznie lepsze, a złożona sekwencja operacji grupowania została zredukowana do pojedynczego agregatu strumienia (który nie wymaga pamięci, a zatem nie może się rozlać na dysk).
Trudno jest powiedzieć, że wydajność będzie rzeczywiście być lepiej w tym przypadku z dodatkowym tabeli tymczasowej, ale szacunki i wybory plan będzie znacznie bardziej odporne na zmiany objętości i dystrybucji danych w czasie. W dłuższej perspektywie może to być cenniejsze niż niewielki wzrost wydajności. W każdym razie masz teraz znacznie więcej informacji, na których możesz oprzeć swoją ostateczną decyzję.
#temp
tworzenie i użytkowanie będą stanowić problem dla wydajności, a nie zysk. Zapisujesz do nieindeksowanej tabeli, z której możesz skorzystać tylko raz. Spróbuj całkowicie go usunąć (i ewentualnie zmień toin (select id from #temp)
naexists
podkwerendę.