2014-09-30 5 views
6

Muszę wybrać wiersze z tabeli BUNDLES, które mają jedną z kilku wartości SAP_STATE_ID. Wartości te zależą od tego, czy dany status SAP ma zostać wyeksportowany, czy nie.Dlaczego podzapytanie i dołączanie są tak powolne?

Ta kwerenda działa bardzo szybko (istnieje indeks na polu SAP_STATE_ID) -

SELECT b.* FROM BUNDLES b WHERE b.SAP_STATE_ID IN (2,3,5,6) 

Ale ... Chciałbym listę identyfikatorów pobierać dynamicznie, tak:

SELECT b.* FROM BUNDLES b 
WHERE b.SAP_STATE_ID IN 
(SELECT s.SAP_STATE_ID FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1) 

I ouch, to zapytanie nagle zajmuje zbyt dużo czasu. Spodziewam się, że najpierw serwer SQL uruchomi podzapytanie (nie zależy to od niczego z głównej kwerendy), a następnie uruchomi całą sprawę tak, jak w moim pierwszym przykładzie. Próbowałem przepisać go, aby użyć połączeń zamiast podzapytania:

SELECT b.* FROM BUNDLES b 
JOIN SAP_STATES s ON (s.SAP_STATE_ID = b.SAP_STATE_ID) 
WHERE s.EXPORT_TO_SAP = 1 

, ale ma tę samą słabą wydajność. Wygląda na to, że uruchamia podzapytanie dla każdego wiersza tabeli BUNDLES lub coś podobnego. Nie mam umiejętności czytania planów wykonania, ale próbowałem. Mówi się, że 81% koszt jest przeznaczony na skanowanie Primary key index BUNDLES (nie mam pojęcia, dlaczego powinien to zrobić, jest pole BUNDLE_ID zdefiniowane jako PRIMARY KEY, ale w ogóle nie pojawia się w zapytaniu ...)

Czy ktoś ma wyjaśnienie, dlaczego serwer SQL jest tak "głupi"? Czy istnieje sposób na osiągnięcie tego, co chcę, przy dobrej wydajności, ale bez potrzeby podawania statycznej listy identyfikatorów SAP_STATE_ID?

scenariusz dla obu tabel i odpowiednich indeksów - Plan http://mab.to/xbYiI0wKj

wykonania dla wersji podzapytanie - http://mab.to/8Qh6gpdYZ

planu kwerend dla wersji ze sprzężeń - http://mab.to/YCqeGCUbr

(z jakiegoś powodu te dwa plany wygląda tak samo i oba sugerują utworzenie indeksu BUNDLES.SAP_STATE_ID, który już istnieje)

+0

Czy masz indeksu na SAP_STATES.SAP_STATE_ID? –

+0

tak, jest to klucz podstawowy faktycznie – lot

+0

Jak długo trwa wykonywanie podkwerendy? – DavidG

Odpowiedz

3

Jestem całkiem pewny statystyki są wyłączone na stołach.Jeśli chcesz, aby to działa w pośpiechu chciałbym napisać kwerendę jako:

SELECT b.* 
    FROM SAP_STATES s 
INNER LOOP JOIN BUNDLES b 
    ON s.SAP_STATE_ID = b.SAP_STATE_ID 
WHERE s.EXPORT_TO_SAP = 1 

zmusza to zagnieżdżone pętle przyłączenia nad SAP_STATES który filtruje na BUNDLES

+0

tak, to załatwiło sprawę. Wstydzę się, że nigdy wcześniej nie słyszałem o takiej opcji. Muszę się dowiedzieć czegoś więcej na temat tej pętli LOOP. – lot

+0

czy nie podważam tego, że tworzenie właściwych statystyk powinno sprawić, że optymalizator wybierze właściwy typ sprzężenia, a zatem słowo kluczowe LOOP nie będzie tutaj potrzebne? – lot

+0

Rzeczywiście. Jestem pewien, że jeśli uruchomisz zapytanie z planem pokazowym, zobaczysz, że szacunkowa i efektywna liczba zwróconych wierszy będzie bardzo różna. Zazwyczaj aktualizacja statystyk będzie wystarczająca do rozwiązania problemu. W niektórych przypadkach musisz robić bardziej egzotyczne rzeczy, takie jak "optymalizacja na nieznane", ale nie sądzę, że jest to potrzebne tutaj. –

0

Ponieważ z jakiegoś powodu problemy z przejściem na mab.to,

Proponuję zapewnić następujące

table  index 
sap_states (export_to_sap, sap_state_id) 
bundles  (sap_state_id) 

select 
     b.* 
    from 
     sap_states ss 
     join bundles b 
      on ss.sap_state_id = b.sap_state_id 
    where 
     ss.export_to_sap = 1 
+0

Próbowałem przepisać zapytanie za pomocą JOIN (jak opisano w pytaniu), ale z bez powodzenia. Twoje zapytanie ma tylko zamienioną kolejność pakietów i sap_states tabel - próbowałem tego i pozostaje takie samo. – lot

+0

Czy utworzyć indeks na 'bundles.sap_state_id'? Ponadto opublikuj plan wyjaśniający dla swojego zapytania za pomocą sprzężenia. – Andrew

+0

tak, istnieje taki plan indeksu – lot

2

Podczas korzystania z tabel (tymczasowe lub fizycznych), silnik SQL buduje statystyk przeciwko nim, a więc ma bardzo jasne wyobrażenie o liczbie wierszy w nim, a które jest najlepszym podejściem do wykonania dla niego. Z drugiej strony, obliczona tabela (sub zapytanie) nie ma statystyk przeciwko niemu.

Podczas gdy człowiek może wydawać się prosty, aby wydedukować liczbę wierszy w nim, "głupi" Silnik SQL nie jest tego świadomy. Teraz, przychodząc do zapytania, klauzula WHERE s.EXPORT_TO_SAP = 1 robi tu wielką różnicę. Indeks klastrowany jest posortowany i zbudowany na SAP_STATE_ID, ale aby dodatkowo sprawdzić klauzulę WHERE, nie ma opcji, ale przeskanować całą tabelę (w ostatecznym zbiorze danych)! Założę się, że gdyby zamiast indeksu klastrowanego, gdyby istniał indeks klastrowany bez klastrów w kolumnie SAP_STATE_ID, który obejmował pole EXPORT_TO_SAP, mógł to zrobić. Od skupionych skany indeksy są na ogół złe dla wydajności, chciałbym zaproponować Ci podjąć poniżej podejścia:

SELECT s.SAP_STATE_ID 
into #Sap_State 
FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1 

SELECT b.* FROM BUNDLES b 
join #Sap_State a on a.sap_state_id = b.sap_state_id 
+0

tak, to podejście poprawia wydajność, awansowałem. Ale używanie tymczasowego stołu wydaje mi się w tym przypadku trochę dziwne. Aby być w 100% czysty, powinien również pojawić się DROP TABLE #Sap_state po wyborze. W ten sposób stworzyliśmy oryginalny liniowiec na trzy linie, z których nie bardzo się cieszę. – lot

+0

BTW, dlaczego to podejście działa, a inne nie? Nie rozumiem, dlaczego posiadanie wartości w tabeli tymczasowej jest szybsze niż posiadanie ich w zestawie rekordów, który pojawił się w wyniku podkwerendy. Czy chodzi tylko o optymalizator, który nie buduje odpowiedniego planu wykonania w innych przypadkach? – lot

+0

Zmieniono moją odpowiedź z wyjaśnieniem. – SouravA