W przypadku tylko 400 stacji zapytanie to będzie znacznie szybsze:
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM station s
CROSS JOIN LATERAL (
SELECT submitted_at, level_sensor
FROM station_logs
WHERE station_id = s.station_id
ORDER BY submitted_at DESC NULLS LAST
LIMIT 1
) l;
dbfiddle tutaj
(porównując plany dla tego zapytania, alternatywa Abelisto i twoje oryginalne)
Wynikające z EXPLAIN ANALYZEdostarczonych przez PO:
Zagnieżdżona pętla (koszt = 0,56..356,65 wierszy = 102 szerokość = 20) (rzeczywisty czas = 0,034..0,979 wierszy = 98 pętli = 1)
-> Seq Scan na stacjach s (koszt = 0,00..3,02 wierszy = 102 szerokość = 4) (czas rzeczywisty = 0,009..0,016 wierszy = 102 pętle = 1)
-> Limit (koszt = 0,56..3,45 wiersza = 1 szerokość = 16) (rzeczywisty czas = 0,009..0,009 wierszy = 1 pętla = 102)
-> Skanowanie indeksu za pomocą id_partycji_przesłane_ na logach stacji (koszt = 0,56..664062,38 wierszy = 230223 szerokość = 16) (czas rzeczywisty = 0,009 $
Indeks Cond: (station_id = s.id)
Czas planowania: 0,542 ms
Czas wykonania: 1,013 ms - !!
Jedyny wskaźnik potrzebne jest utworzony jeden: station_id__submitted_at. Zasadniczo UNIQUEograniczenie uniq_sid_satrównież działa. Utrzymanie obu wydaje się marnowaniem miejsca na dysku i wydajnością zapisu.
Dodałem NULLS LASTdo ORDER BYzapytania, ponieważ submitted_atnie jest zdefiniowane NOT NULL. Idealnie, jeśli ma to zastosowanie !, dodaj NOT NULLograniczenie do kolumny submitted_at, usuń dodatkowy indeks i usuń NULLS LASTz zapytania.
Jeśli submitted_atto możliwe NULL, utwórz ten UNIQUEindeks, aby zastąpić zarówno bieżący indeks, jak i unikalne ograniczenie:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Rozważać:
Zakłada się oddzielną tabelęstation z jednym wierszem na odpowiedni station_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:
CREATE TABLE station AS
WITH RECURSIVE cte AS (
(
SELECT station_id
FROM station_logs
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT l.station_id
FROM cte c
, LATERAL (
SELECT station_id
FROM station_logs
WHERE station_id > c.station_id
ORDER BY station_id
LIMIT 1
) l
)
TABLE cte;
Używam tego również w skrzypcach. Możesz użyć podobnego zapytania, aby rozwiązać swoje zadanie bezpośrednio, bez stationtabeli - 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_sensorjako 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ś:
... około 20 000 wierszy dziennie station_id.
Zazwyczaj oznaczałoby to ciągłe obciążenie zapisu (1 na station_id5 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 agresywne VACUUMustawienia, 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 dodawaj level_sensordo 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:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
Wybierz znacznik czasu, dla którego wiesz, że muszą istnieć młodsze wiersze. Musisz dodać pasujący WHEREwarunek do wszystkich zapytań, na przykład:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Od czasu do czasu musisz dostosowywać indeks i zapytania.
Powiązane odpowiedzi z dodatkowymi szczegółami: