2013-07-10 7 views
8

Mam zapytanie o system Postgresql 9.2, który zajmuje około 20 w swojej normalnej postaci, ale zajmuje tylko ~ 120 ms przy użyciu CTE.Czy istnieje logicznie równoważna i wydajna wersja tego zapytania bez użycia CTE?

Uprościliśmy oba zapytania pod kątem zwięzłości.

Oto postaci normalnej (trwa około 20 lat)

SELECT * 
FROM tableA 
WHERE (columna = 1 OR columnb = 2) AND 
    atype = 35 AND 
    aid IN (1, 2, 3) 
ORDER BY modified_at DESC 
LIMIT 25; 

Oto wyjaśnienia tego zapytania: http://explain.depesz.com/s/2v8

Postać CTE (około 120ms):

WITH raw AS (
    SELECT * 
    FROM tableA 
    WHERE (columna = 1 OR columnb = 2) AND 
     atype = 35 AND 
     aid IN (1, 2, 3) 
) 
SELECT * 
FROM raw 
ORDER BY modified_at DESC 
LIMIT 25; 

Oto wyjaśnienie CTE: http://explain.depesz.com/s/uxy

Po prostu przesuwając ORDER BY do zewnętrznej części zapytania zmniejsza koszt o 99%.

Mam dwa pytania: 1) czy istnieje sposób na skonstruowanie pierwszego zapytania bez użycia CTE w taki sposób, aby był logicznie równoważny bardziej wydajny i 2) co ta różnica w wydajności mówi o tym, jak planista jest ustalanie sposobu pobierania danych?

Co do powyższych pytań, czy istnieją dodatkowe statystyki lub inne wskazówki dla planistów, które pomogłyby poprawić wydajność pierwszego zapytania?


Edytuj: Usunięcie limitu powoduje również, że w zapytaniu używane jest skanowanie sterty, a nie skanowanie indeksu do tyłu. Bez zapytania LIMIT zapytanie kończy się w ciągu 40 ms.

Po obejrzeniu efektu LIMIT Próbowałem z LIMIT 1, LIMIT 2 itd Zapytanie wykonuje w ramach 100ms przy użyciu LIMIT 1 i 10s + z LIMIT> 1.

Po myśleć o tym trochę więcej, pytanie 2 wrze do tego, dlaczego planista używa skanowania indeksu do tyłu w jednym przypadku, a stertę bitmapową skanuje i sortuje w innym logicznie równoważnym przypadku? A jak mogę "pomóc" planiście w efektywnym planowaniu w obu przypadkach?


Aktualizacja: przyjąłem odpowiedź Craiga, ponieważ był to najbardziej wszechstronna i pomocny. Sposób w jaki rozwiązałem problem polegał na zastosowaniu zapytania, które było praktycznie równoważne, ale nie logicznie równoważne. U źródła problemu znajdował się indeks skanowany do tyłu indeksu na modified_at. Aby poinformować planistę, że nie jest to dobry pomysł, dodam predykat formularza WHERE modified_at >= NOW() - INTERVAL '1 year'. Obejmowało to wystarczającą ilość danych dla aplikacji, ale uniemożliwiło planerowi przejście w dół ścieżki skanowania indeksu wstecznego.

To było znacznie mniej skuteczne rozwiązanie, które zapobiegło konieczności przepisywania zapytań za pomocą sub zapytania lub CTE. YMMV.

Odpowiedz

10

Oto dlaczego tak się dzieje, z następującym wyjaśnieniem prądu co najmniej do 9.3 (jeśli to czytasz i na nowszej wersji, upewnij się, że nie uległa zmianie):

PostgreSQL nie optymalizuje ponad granicami CTE. Każda klauzula CTE działa w izolacji, a jej wyniki są wykorzystywane przez inne części zapytania. Tak więc zapytanie takie jak:

WITH blah AS (
    SELECT * FROM some_table 
) 
SELECT * 
FROM blah 
WHERE id = 4; 

spowoduje wykonanie pełnej wewnętrznej kwerendy. PostgreSQL nie "sprowadzi" kwalifikacji do id = 4 do zapytania wewnętrznego. CTE to "płoty optymalizacyjne" w tym względzie, które mogą być zarówno dobre, jak i złe; Pozwala zastąpić planner, kiedy chcesz, ale uniemożliwia korzystanie z CTE jako prosty składniowej porządki dla głęboko zagnieżdżonych łańcucha FROM podzapytanie Jeśli musisz push-down.

Jeśli rephrase wyżej jako:

SELECT * 
FROM (SELECT * FROM some_table) AS blah 
WHERE id = 4; 

za pomocą podzapytania w FROM zamiast CTE, str pchnie qual dół do podzapytania i wszystko będzie działać ładnie i szybko.

Jak odkryli, może również pracować do korzyści, gdy kwerenda Planowanie sprawia słabe decyzję. Wydaje się, że w przypadku, gdy w tył skanowania indeksu tabeli jest ogromnie droższe bitmapą lub indeks skanowanie dwóch mniejszych indeksach następnie przez filtr i rodzaju, ale planista nie że będzie tak, że planuje zapytania aby zeskanować indeks.

Gdy używasz CTE, to nie możesz wepchnąć ORDER BY do wewnętrznego zapytania, więc zmieniasz jego plan i zmuszasz go do użycia tego, co według niego jest gorszym planem wykonania - ale który okazuje się być dużo lepiej.

Istnieje nieprzyjemne obejście, które można zastosować w takich sytuacjach, nazywane hakowaniem OFFSET 0, ale należy go używać tylko wtedy, gdy nie można znaleźć sposobu, aby planista zrobił to, co trzeba - i jeśli trzeba użyć to, proszę ugotuj to do samodzielnego przypadku testowego i zgłoś to na liście mailingowej PostgreSQL jako ewentualny błąd planisty zapytań.

Zamiast tego polecam najpierw przejrzeć , dlaczego planista podejmuje niewłaściwą decyzję.

Pierwszy kandydat statystyki/szacunków problemy, i na pewno wystarczy, gdy patrzymy na swojej problematycznej planu kwerend Jest czynnikiem 3500 niewłaściwego oszacowania oczekiwanych wierszy wynikowych. To duże, ale nie niemożliwie duże, chociaż bardziej interesujące jest to, że dostajesz tylko jeden wiersz, w którym planista oczekuje nietrywialnego zestawu wierszy. To jednak niewiele nam pomoże; jeśli liczba wierszy jest niższa niż oczekiwana, oznacza to, że wybór opcji korzystania z indeksu był lepszy niż oczekiwano.

Głównym problemem wygląda to nie użyciu mniejszych, bardziej selektywnych indeksy sierra_kilo i papa_lima ponieważ widzi ORDER BY i uważa, że ​​będzie to zaoszczędzić więcej czasu robi wsteczną skanowanie indeksu i unikanie rodzaju niż to naprawdę robi . Ma to sens, biorąc pod uwagę, że istnieje tylko jeden zgodny rząd do sortowania! Jeśli otrzyma oczekiwane 3500 wierszy, to może być bardziej sensownym unikaniem sortowania, chociaż to wciąż dość mały zestaw wierszy, aby po prostu sortować w pamięci.

Czy można ustawić wszelkie parametry jak enable_seqscan, etc? Jeśli to zrobisz, rozwiąż je; są one przeznaczone wyłącznie do testowania i są całkowicie nieodpowiednie do użytku produkcyjnego.Jeśli nie korzystasz z paramerów enable_, myślę, że warto o tym wspomnieć na liście mailingowej PostgreSQL pgsql-perform. Anonimowe plany sprawiają jednak, że jest to trochę trudne, zwłaszcza że nie ma gwarancji, że identyfikatory z jednego planu odnoszą się do tych samych obiektów w drugim planie i nie pasują do tego, co napisałeś w zapytaniu na pytanie. Będziesz chciał stworzyć poprawnie wykonaną wersję, w której wszystko pasuje, zanim zadasz pytanie na liście mailingowej.

Istnieje spora szansa, że ​​będziesz musiał podać prawdziwe wartości , aby każdy mógł Ci pomóc. Jeśli nie chcesz tego robić na publicznej liście mailingowej, there's another option available. (Powinienem zauważyć, że pracuję dla jednego z nich, według mojego profilu).

+0

dziękuję, chociaż użyłem tej własności (jest to przypadek) Nie wiedziałem, że PostgreSQL nie optymalizuje się na granicach CTE. Jeśli spojrzysz na przedstawione przeze mnie plany wyjaśniające, nie wydaje ci się, że używane są znaczące ilości 'work_mem' (~ 25k). Większość kosztów pochodzi ze skanowania indeksu wstecz. – drsnyder

+0

@drsnyder Oh! Źle przeczytałem! 20s i 120ms. Ponownie przeczytam i poprawię odpowiedź odpowiednio. –

+0

@drsnyder Ponownie napisane. Nadzieja, która ma więcej sensu. Proszę pokazać wyjście z http://wiki.postgresql.org/wiki/Server_Configuration tylko po to, aby potwierdzić, że nie masz żadnych parautów 'enable_', itp., Ale wygląda to trochę na podejrzany wybór przez planistę. –

2

Po prostu strzał w ciemno, ale to, co się dzieje, jeśli uruchomić ten

SELECT * 
FROM (
    SELECT * 
    FROM tableA 
    WHERE (columna = 1 OR columnb = 2) AND 
     atype = 35 AND 
     aid IN (1, 2, 3) 
) AS x 
ORDER BY modified_at DESC 
LIMIT 25; 
Powiązane problemy