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 ANALYZE
dostarczonych 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 UNIQUE
ograniczenie uniq_sid_sat
również działa. Utrzymanie obu wydaje się marnowaniem miejsca na dysku i wydajnością zapisu.
Dodałem NULLS LAST
do ORDER BY
zapytania, ponieważ submitted_at
nie jest zdefiniowane NOT NULL
. Idealnie, jeśli ma to zastosowanie !, dodaj NOT NULL
ograniczenie do kolumny submitted_at
, usuń dodatkowy indeks i usuń NULLS LAST
z zapytania.
Jeśli submitted_at
to możliwe NULL
, utwórz ten UNIQUE
indeks, 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 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ś:
... około 20 000 wierszy dziennie station_id
.
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 agresywne VACUUM
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 dodawaj level_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:
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 WHERE
warunek 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: