Skanowanie indeksu (tylko) --> Skanowanie indeksu mapy bitowej --> Skanowanie sekwencyjne
Dla kilku wierszy opłaca się uruchomić skanowanie indeksu. Jeśli wystarczająca liczba stron danych jest widoczna dla wszystkich (=wystarczająco odkurzona i niezbyt duże współbieżne obciążenie zapisu), a indeks może zapewnić wszystkie potrzebne wartości kolumn, używane jest szybsze skanowanie tylko indeksu. Przy większej liczbie wierszy, które mają zostać zwrócone (wyższy procent tabeli i w zależności od rozkładu danych, częstości wartości i szerokości wiersza), bardziej prawdopodobne staje się znalezienie kilku wierszy na jednej stronie danych. Wtedy opłaca się przełączyć na skanowanie indeksu bitmapy. (Lub połączyć wiele odrębnych indeksów). Gdy i tak trzeba odwiedzić duży procent stron danych, taniej jest uruchomić skanowanie sekwencyjne, filtrować nadmiarowe wiersze i całkowicie pomijać narzuty na indeksy.
Użycie indeksu staje się (znacznie) tańsze i bardziej prawdopodobne, gdy dostęp do stron danych w kolejności losowej nie jest (znacznie) droższy niż dostęp do nich w kolejności sekwencyjnej. Tak jest w przypadku używania SSD zamiast wirowania dysków, a tym bardziej, im więcej jest buforowane w pamięci RAM - i odpowiednie parametry konfiguracyjne random_page_cost
i effective_cache_size
są odpowiednio ustawione.
W Twoim przypadku Postgres przełącza się na skanowanie sekwencyjne, oczekując na znalezienie rows=263962
, to już 3% całej tabeli. (Podczas gdy tylko rows=47935
są rzeczywiście znalezione, patrz poniżej.)
Więcej w tej powiązanej odpowiedzi:
- Wydajne zapytanie PostgreSQL na znaczniku czasu przy użyciu indeksu lub skanowania indeksu bitmapy?
Strzeż się wymuszania planów zapytań
Nie możesz wymusić określonej metody planowania bezpośrednio w Postgresie, ale możesz utworzyć inną metody wydają się bardzo drogie do celów debugowania. Zobacz Konfiguracja metody planowania w podręczniku.
SET enable_seqscan = off
(jak sugerowano w innej odpowiedzi) robi to z sekwencyjnymi skanami. Ale jest to przeznaczone tylko do celów debugowania w twojej sesji. Czy nie użyj tego jako ogólnego ustawienia w produkcji, chyba że wiesz dokładnie, co robisz. Może wymusić absurdalne plany zapytań. Instrukcja:
Te parametry konfiguracji zapewniają surową metodę wpływania na plany zapytań wybrane przez optymalizator zapytań. Jeśli domyślny plan wybrany przez optymalizator dla konkretnego zapytania nie jest optymalny, tymczasowy rozwiązaniem jest użycie jednego z tych parametrów konfiguracyjnych, aby wymusić na optymalizatorze wybór innego planu. Lepsze sposoby poprawy jakości planów wybranych przez optymalizator obejmują dostosowanie stałych kosztów planisty (patrz Rozdział 19.7.2), uruchomienie ANALYZE
ręcznie, zwiększając wartość default_statistics_target
parametr konfiguracyjny i zwiększenie ilości statystyk zbieranych dla określonych kolumn przy użyciu ALTER TABLE SET STATISTICS
.
To już większość porad, których potrzebujesz.
- Nie pozwól PostgreSQL-owi czasami wybierać złego planu zapytań
W tym konkretnym przypadku Postgres spodziewa się 5-6 razy więcej trafień na email_activities.email_recipient_id
niż są faktycznie znalezione:
szacowany rows=227007
a actual ... rows=40789
szacowany rows=263962
a actual ... rows=47935
Jeśli często uruchamiasz to zapytanie, opłaca się mieć ANALYZE
spójrz na większą próbkę, aby uzyskać dokładniejsze statystyki dotyczące konkretnej kolumny. Twój stół jest duży (~ 10 mln rzędów), więc zrób to:
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
Następnie ANALYZE email_activities;
Ostateczność
W bardzo rzadko przypadki, w których możesz wymusić indeksowanie za pomocą SET LOCAL enable_seqscan = off
w oddzielnej transakcji lub w funkcji z własnym otoczeniem. Na przykład:
CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
RETURNS bigint AS
$func$
SELECT COUNT(DISTINCT a.email_recipient_id)
FROM email_activities a
WHERE a.email_recipient_id IN (
SELECT id
FROM email_recipients
WHERE email_campaign_id = $1
LIMIT $2) -- or consider query below
$func$ LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;
Ustawienie dotyczy tylko lokalnego zakresu funkcji.
Ostrzeżenie: To tylko dowód koncepcji. Nawet ta znacznie mniej radykalna interwencja ręczna może cię ugryźć na dłuższą metę. Liczności, częstotliwości wartości, Twój schemat, globalne ustawienia Postgresa, wszystko zmienia się w czasie. Zamierzasz uaktualnić do nowej wersji Postgres. Plan zapytań, który teraz wymuszasz, może później stać się bardzo złym pomysłem.
I zazwyczaj jest to tylko obejście problemu z konfiguracją. Lepiej go znajdź i napraw.
Alternatywne zapytanie
W pytaniu brakuje istotnych informacji, ale to równoważne zapytanie jest prawdopodobnie szybsze i bardziej prawdopodobne jest użycie indeksu na (email_recipient_id
) - coraz bardziej dla większego LIMIT
.
SELECT COUNT(*) AS ct
FROM (
SELECT id
FROM email_recipients
WHERE email_campaign_id = 1607
LIMIT 43000
) r
WHERE EXISTS (
SELECT FROM email_activities
WHERE email_recipient_id = r.id);