Mam stosunkowo proste zapytanie dotyczące tabeli z 1,5 mln wierszy:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
wynik:
Limit (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1) -> Bitmap Heap Scan on publication (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1) Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321)) -> BitmapOr (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1) -> Bitmap Index Scan on publication_pkey (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1) Index Cond: (mtid = 9762715) -> Bitmap Index Scan on publication_last_modifier_btree (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1) Index Cond: (last_modifier = 21321) Total runtime: 1.027 ms
Jak dotąd dobry, szybki i wykorzystuje dostępne indeksy.
Teraz, jeśli nieco zmodyfikuję zapytanie, wynikiem będzie:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
Dane EXPLAIN ANALYZE
wyjściowe to:
Limit (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1) -> Seq Scan on publication (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1) Filter: ((hashed SubPlan 1) OR (last_modifier = 21321)) SubPlan 1 -> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1) Total runtime: 2841.442 ms
Nie tak szybko i przy użyciu skanowania sekwencyjnego ...
Oczywiście oryginalne zapytanie uruchamiane przez aplikację jest nieco bardziej złożone, a nawet wolniejsze, i oczywiście nie jest generowany hibernacja (SELECT 9762715)
, ale powolność jest nawet do tego (SELECT 9762715)
! Zapytanie jest generowane przez hibernację, więc ich zmiana jest dość trudna, a niektóre funkcje nie są dostępne (np. UNION
Nie są dostępne, co byłoby szybkie).
Pytania
- Dlaczego nie można użyć indeksu w drugim przypadku? Jak można je wykorzystać?
- Czy mogę poprawić wydajność zapytań w inny sposób?
Dodatkowe przemyślenia
Wydaje się, że moglibyśmy użyć pierwszego przypadku, ręcznie wykonując WYBÓR, a następnie umieszczając wynikową listę w zapytaniu. Nawet przy 5000 liczb na liście IN () jest czterokrotnie szybszy niż drugie rozwiązanie. Wydaje się to jednak NIEPRAWIDŁOWE (może być 100 razy szybsze :)). Jest całkowicie niezrozumiałe, dlaczego planista zapytań stosuje zupełnie inną metodę dla tych dwóch zapytań, dlatego chciałbym znaleźć lepsze rozwiązanie tego problemu.
JOIN
zamiast niegoIN ()
? Ponadtopublication
został niedawno przeanalizowany?(SELECT 9762715)
.(SELECT 9762715)
. Na pytanie o hibernację: można to zrobić, ale wymaga poważnego przepisania kodu, ponieważ mamy zdefiniowane przez użytkownika zapytania o hibernację, które są tłumaczone w locie. Zasadniczo zmodyfikowalibyśmy hibernację, która jest ogromnym przedsięwzięciem z wieloma możliwymi skutkami ubocznymi.Odpowiedzi:
Rdzeń problemu staje się tutaj oczywisty:
Szacunki Postgres zwracają 744661 wierszy, podczas gdy w rzeczywistości okazuje się, że jest to jeden wiersz. Jeśli Postgres nie wie lepiej, czego oczekiwać od zapytania, nie może lepiej zaplanować. Musielibyśmy zobaczyć rzeczywiste zapytanie ukryte za nimi
(SELECT 9762715)
- i prawdopodobnie także znalibyśmy definicję tabeli, ograniczenia, liczności i rozkład danych. Oczywiście, Postgres nie jest w stanie przewidzieć, jak niewiele wierszy zostanie zwrócony przez nią. Mogą istnieć sposoby na przepisanie zapytania, w zależności od tego, co to jest .Jeśli wiesz, że podzapytanie nigdy nie może zwrócić więcej niż
n
wierszy, możesz po prostu powiedzieć Postgres, używając:Jeśli n jest wystarczająco małe, Postgres przełączy się na skanowanie indeksu (bitmapy). Działa to jednak tylko w prostym przypadku. Przestaje działać podczas dodawania
OR
warunku: narzędzie do planowania zapytań nie może obecnie sobie z tym poradzić.Rzadko używam
IN (SELECT ...)
na początek. Zazwyczaj istnieje lepszy sposób na wdrożenie tego samego, często zEXISTS
łączeniem częściowym. Czasami za pomocą (LEFT
)JOIN
(LATERAL
) ...Oczywistym obejściem byłoby użycie
UNION
, ale wykluczyłeś to. Nie mogę powiedzieć więcej, nie znając faktycznego podzapytania i innych istotnych szczegółów.źródło
(SELECT 9762715)
! Jeśli uruchomię dokładnie to zapytanie, które widzisz powyżej. Oczywiście pierwotne zapytanie hibernacji jest nieco bardziej skomplikowane, ale (myślę, że) udało mi się wskazać, gdzie planista zapytań popełnił błąd, więc przedstawiłem tę część zapytania. Jednak powyższe wyjaśnienia i zapytania są dosłownie ctrl-cv.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
wykonuje również skanowanie sekwencyjne i działa również przez około 3 sekundy ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. I efekt nadal występował w przypadku tych samych zapytańtest
: każde kwerendy skutkowało skanem seq ... Próbowałem zarówno 9.1, jak i 9.4. Efekt jest taki sam.OR
warunku. Sztuczka zLIMIT
działa tylko w prostszym przypadku.Mój kolega znalazł sposób na zmianę zapytania, tak aby wymagało to prostego przepisania i robi to, co musi zrobić, tj. Wykonuje podselekcję w jednym kroku, a następnie wykonuje dalsze operacje na wyniku:
Wyjaśnij teraz analizę:
Wydaje się, że możemy stworzyć prosty parser, który wyszukuje i przepisuje wszystkie podselekcje w ten sposób, i dodaje go do hibernacji, aby manipulować rodzimym zapytaniem.
źródło
SELECT
s, jak w pierwszym zapytaniu w pytaniu?SELECT
osobno, a następnie wybierz zewnętrzny z statyczną listą poIN
. Jest to jednak znacznie wolniejsze (5-10 razy, jeśli podkwerenda ma więcej niż kilka wyników), ponieważ masz dodatkowe objazdy w sieci oraz formatowanie wielu wyników przez postgres, a następnie parsowanie tych wyników przez java (a następnie wykonanie to samo znowu do tyłu). Powyższe rozwiązanie działa tak samo semantycznie, pozostawiając proces wewnątrz postgresu. Podsumowując, obecnie wydaje się to najszybszy sposób z najmniejszą modyfikacją w naszym przypadku.Odpowiedz na drugie pytanie: Tak, możesz dodać ORDER BY do swojego podzapytania, co będzie miało pozytywny wpływ. Ale podobieństwo do rozwiązania „ISTNIEJE (podzapytanie)” pod względem wydajności. Istnieje znacząca różnica nawet przy podzapytaniu skutkującym dwoma wierszami.
źródło