Mam tabelę station_logs
w bazie danych PostgreSQL 9.6:
Column | Type |
---------------+-----------------------------+
id | bigint | bigserial
station_id | integer | not null
submitted_at | timestamp without time zone |
level_sensor | double precision |
Indexes:
"station_logs_pkey" PRIMARY KEY, btree (id)
"uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)
Próbuję uzyskać ostatnią level_sensor
wartość na podstawiesubmitted_at
dla każdego station_id
. Istnieje około 400 unikalnych station_id
wartości i około 20 000 wierszy dziennie station_id
.
Przed utworzeniem indeksu:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Unikalny (koszt = 4347852.14..4450301.72 wierszy = 89 szerokość = 20) (czas rzeczywisty = 22202.080..27619.167 wierszy = 98 pętli = 1) -> Sortuj (koszt = 4347852.14..4399076.93 wierszy = 20489916 szerokość = 20) (rzeczywisty czas = 22202.077..26540.827 wierszy = 20489812 pętli = 1) Klucz sortowania: identyfikator stacji, przesłany na DESC Metoda sortowania: scalanie zewnętrzne Dysk: 681040kB -> Seq Scan na logach stacji (koszt = 0,00..598895.16 wierszy = 20489916 szerokość = 20) (rzeczywisty czas = 0,023..3443,587 wierszy = 20489812 pętli = $ Czas planowania: 0,072 ms Czas wykonania: 27690,644 ms
Tworzenie indeksu:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Po utworzeniu indeksu dla tego samego zapytania:
Unikalny (koszt = 0,56..2156367,51 wierszy = 89 szerokość = 20) (rzeczywisty czas = 0,184..16263,413 wierszy = 98 pętli = 1) -> Skanowanie indeksu za pomocą id_partycji_przesłane_ na logach stacji (koszt = 0,56..2105142,98 wierszy = 20489812 szerokość = 20) (czas rzeczywisty = 0,181..1 $ Czas planowania: 0,206 ms Czas wykonania: 16263,490 ms
Czy istnieje sposób na przyspieszenie tego zapytania? Na przykład 1 sekunda, 16 sekund to wciąż za dużo.
Odpowiedzi:
W przypadku tylko 400 stacji zapytanie to będzie znacznie szybsze:
dbfiddle tutaj
(porównując plany dla tego zapytania, alternatywa Abelisto i twoje oryginalne)
Wynikające z
EXPLAIN ANALYZE
dostarczonych przez PO:Jedyny wskaźnik potrzebne jest utworzony jeden:
station_id__submitted_at
. ZasadniczoUNIQUE
ograniczenieuniq_sid_sat
również działa. Utrzymanie obu wydaje się marnowaniem miejsca na dysku i wydajnością zapisu.Dodałem
NULLS LAST
doORDER BY
zapytania, ponieważsubmitted_at
nie jest zdefiniowaneNOT NULL
. Idealnie, jeśli ma to zastosowanie !, dodajNOT NULL
ograniczenie do kolumnysubmitted_at
, usuń dodatkowy indeks i usuńNULLS LAST
z zapytania.Jeśli
submitted_at
to możliweNULL
, utwórz tenUNIQUE
indeks, aby zastąpić zarówno bieżący indeks, jak i unikalne ograniczenie:Rozważać:
Zakłada się oddzielną tabelę
station
z jednym wierszem na odpowiednistation_id
(zwykle PK) - co powinieneś mieć w obu przypadkach. Jeśli go nie masz, utwórz go. Ponownie, bardzo szybko dzięki tej technice rCTE:Używam tego również w skrzypcach. Możesz użyć podobnego zapytania, aby rozwiązać swoje zadanie bezpośrednio, bez
station
tabeli - jeśli nie możesz przekonać się do jego utworzenia.Szczegółowe instrukcje, wyjaśnienia i alternatywy:
Zoptymalizuj indeks
Twoje zapytanie powinno być teraz bardzo szybkie. Tylko jeśli nadal musisz zoptymalizować wydajność odczytu ...
Warto dodać
level_sensor
jako ostatnią kolumnę do indeksu, aby umożliwić skanowanie tylko indeksu , tak jak komentował joanolo .Przeciw: zwiększa indeks - co powoduje niewielkie koszty dla wszystkich zapytań, które go wykorzystują.
Pro: Jeśli faktycznie skanujesz z niego tylko indeksy, zapytanie w ogóle nie musi odwiedzać stron sterty, co czyni je około dwa razy szybszym. Ale może to być nieistotna korzyść dla bardzo szybkiego zapytania.
Jednak nie oczekuję, że zadziała w twojej sprawie. Wspomniałeś:
Zazwyczaj oznaczałoby to ciągłe obciążenie zapisu (1 na
station_id
5 sekund). I jesteś zainteresowany najnowszym wierszem. Skanowania tylko za pomocą indeksu działają tylko w przypadku stron sterty, które są widoczne dla wszystkich transakcji (bit w mapie widoczności jest ustawiony). Trzeba będzie uruchomić bardzo agresywneVACUUM
ustawienia, aby tabela nadążyła za obciążeniem zapisu, i nadal nie będzie działać przez większość czasu. Jeśli moje założenia są prawidłowe, skanowanie tylko do indeksu jest wyłączone, nie dodawajlevel_sensor
do indeksu.OTOH, jeśli moje założenia przytrzymaj i tabela rośnie bardzo duży , a indeks BRIN może pomóc. Związane z:
Lub jeszcze bardziej wyspecjalizowany i bardziej wydajny: częściowy indeks tylko najnowszych dodatków, aby odciąć większość niepotrzebnych wierszy:
Wybierz znacznik czasu, dla którego wiesz, że muszą istnieć młodsze wiersze. Musisz dodać pasujący
WHERE
warunek do wszystkich zapytań, na przykład:Od czasu do czasu musisz dostosowywać indeks i zapytania.
Powiązane odpowiedzi z dodatkowymi szczegółami:
źródło
Wypróbuj klasyczny sposób:
dbfiddle
WYJAŚNIJ ANALIZĘ od ThreadStarter
źródło