2015-05-19 33 views
9

mam tej kwerendy (w PostgreSQL):Query z ORDER BY jest 13 razy wolniejsze kiedy dodać graniczna 1

SELECT "table_1".* FROM "table_1" 
INNER JOIN "join_table" 
    ON "table_1"."id" = "join_table"."table_1_id" 
WHERE "join_table"."table_2_id" = 650727 
ORDER BY table_1.created_at DESC 
LIMIT 1 

która zwraca 1 wynik, ale biorąc ~ 250-300 ms wykonać

Istnieje btree indeksy table_1.created_at, jak również join_table.table_1_id i join_table.table_2_id

Kiedy tylko usunąć LIMIT 1 z zapytania, czas realizacji spada do ~ 13ms. Ta konkretna kwerenda obecnie zwraca tylko jeden wynik (bez LIMIT), ale są inne, które mają inną wartość w WHERE, która może zwrócić więcej (dlatego LIMIT jest konieczny).

Dlaczego dodajemy LIMIT do kwerendy, która już zwraca tylko jeden wynik, tak bardzo obciążając czas wykonania?

Oto wyjaśnienie planu z LIMIT 1 (są zawsze trudne dla mnie, aby w pełni zrozumieć ...): http://explain.depesz.com/s/rOy

A oto wyjaśnienie planu bez ograniczeń 1: http://explain.depesz.com/s/q3d7

Dodatkowo, jeśli Utrzymuję numer LIMIT 1, ale zmienię kolejność na ASC, zapytanie również sprowadza się do 13 ms. A jeśli zmienię LIMIT na LIMIT 20 (Ale zachowaj ORDER BY DESC) zajmie to tylko 22ms ... wtf !?

więc ma coś do czynienia z kombinacją ORDER BY DESC i LIMIT 1 (dokładnie 1)

+0

Wygląda na to, że możesz mieć indeks na kolumnie, którą zamawiasz, a więc kiedy twoje zamówienie jest zgodne z indeksem, jest szybkie, a gdy jest przeciwne do indeksu, musi je zamówić w pamięci, zanim będzie w stanie przedstawić wyniki. Czy możesz wymienić indeksy, które posiadasz na swoich stołach? –

+0

Czy próbowałeś spojrzeć na inne problemy z wydajnością dotyczące zapytań z limitami dla postgresów na stackoverflow? Istnieje wiele tematów, może te pomoc. – Juru

+0

Czy masz 3 indeksy lub 2 z jednym "indeksem złożonym", który zawiera "join_table.table_1_id", a także "join_table.table_2_id"? Z indeksem złożonym filtrowanie sprzężeń może być obsługiwane w całości przez ten indeks. Np .: tworzenie indeksu join_table_ix1 na join_table (table_2_id, table_1_id); –

Odpowiedz

0

Chociaż dodajesz tylko granicę 1, ale każda zmiana zapytania wpływa na jego plan wykonania, a indeksy używane.

Aby rozwiązać problem, skoro mówisz, że gdy zamówienie jest ASC swoją wydajność zapytania jest dobre: ​​

Wydaje indeks tworzony na table_1.created_at jest ASC. Wiem, w db2 można określić podczas tworzenia indeksu do dwukierunkowej ASC/DESC. Domyślam się, że w postgresql powinieneś mieć to samo, jeśli nie, możesz utworzyć 2 indeksy na tym samym polu 1 z sortowaniem DESC i innym z SORT ASC

8

Ok, to całkiem klasyczny przypadek.

Za każdym razem, gdy używasz LIMIT (lub podobnych, takich jak FETCH FIRST ... ROWS ONLY), optymalizator próbuje zoptymalizować zapytanie, tak aby pobieranie tylko pierwszych wierszy było jak najszybsze. Oznacza to, że optymalizator preferuje plany wykonania, w których pierwsza wartość kosztu jest niska, a nie druga, pokazana w planie wykonania. Pamiętaj: dwie wartości kosztów wykazanych przez PostgreSQL (np cost=48.150..6,416.240 są koszty setup (48,150), a całkowity koszt realizacji (6,416.240)

„Problem” jest to, że masz indeks, który wspiera swoją klauzulę ORDER BY.. Tak więc, PostgreSQL myśli, że może po prostu przejść przez ten indeks (w odwrotnej kolejności ze względu na modyfikator DESC w zapytaniu) i sprawdzić dla każdego wiersza w drugiej tabeli, czy spełnia on drugą klauzulę WHERE. Problem polega na tym, że optymalizator nie ma możliwości dowiedzenia się, czy będzie to jeden z pierwszych wierszy, czy raczej jeden na końcu (zgodnie z ORDER BY). Optymalizator arbitralnie odgaduje, że pasujący wiersz będzie bardziej na początku niż na końcu.To optymistyczne oszacowanie jest następnie wykorzystywane do obliczenia wartości kosztowej, która okazuje się zbyt optymistyczna, aby PostgreSQL ostatecznie ustalił zły plan wykonania.

Po zmianie ORDER BY ... DESC na ORDER BY ... ASC optymalizator robi to samo arbitralne, ale optymistyczne oszacowanie, które w tym przypadku okazuje się być bardziej poprawne, dzięki czemu uzyskuje się lepszy czas wykonania.

Jednak z punktu widzenia optymalizacji podstawową przyczyną jest to, że optymalizator szacuje, że 2,491 wiersze będą zgodne z WHERE klauzulą ​​tango = 650727. Gdy optymalizator prawidłowo oszacuje, że trafia on tylko w kilka wierszy, problem prawdopodobnie nie wystąpi.

Klauzula WHERE jest na tyle banalna, że ​​dobre oszacowanie nie powinno stanowić problemu. Główne pytanie brzmi: co z twoimi statystykami na tym stole?

Istnieje kilka sposobów, aby poradzić sobie z tym problemem:

  • zaktualizować swoje statystyki (ANALYZE) i sprawdzić, czy to sam pomaga.
  • Zwiększ liczbę najczęściej używanych wartości przechowywanych dla tej kolumny (ALTER TABLE ... SET STATISTICS). Zwiększa to również wielkość próby używanej do zbierania statystyk, co oznacza, że ​​trwa to dłużej, ale daje dokładniejsze wyniki.

Teoretycznie powinno to wystarczyć, aby rozwiązać ten problem. Jednak inne opcje:

  • Jeśli nie potrzebujemy indeks created_at z innych powodów (jak innych zapytań), pozbyć się go.
  • Ponownie wpisz zapytanie, aby plan złego wykonania nie był już dostępny. W szczególności byłoby wspaniale, gdyby można było napisać zapytanie, aby klauzula ORDER BY używała tej samej tabeli co klauzula WHERE: jeśli masz szczęście, możesz mieć kolumnę o numerze join_table, która ma tę samą kolejność co , więc robi to nie ma żadnej różnicy, którą zamawiasz. Należy jednak zachować ostrożność, łatwo można się pomylić (np. Sekwencyjne liczby wypełnione sekwencjami mogą mieć kontroferty).