Bei nur 400 Stationen ist diese Abfrage erheblich schneller:
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 hier
(Vergleich der Pläne für diese Abfrage, Abelistos Alternative und Ihr Original)
Ergebnis EXPLAIN ANALYZE
wie vom OP bereitgestellt:
Verschachtelte Schleife (Kosten = 0,56..356,65 Zeilen = 102 Breite = 20) (tatsächliche Zeit = 0,034..0,979 Zeilen = 98 Schleifen = 1)
-> Seq Scan auf Stationen s (Kosten = 0,00..3,02 Zeilen = 102 Breite = 4) (tatsächliche Zeit = 0,009..0,016 Zeilen = 102 Schleifen = 1)
-> Limit (Kosten = 0,56..3,45 Zeilen = 1 Breite = 16) (tatsächliche Zeit = 0,009..0,009 Zeilen = 1 Schleifen = 102)
-> Index-Scan mit station_id__submitted_at in station_logs (Kosten = 0,56..664062.38 Zeilen = 230223 Breite = 16) (tatsächliche Zeit = 0,009 $
Index Cond: (station_id = s.id)
Planungszeit: 0,542 ms
Ausführungszeit: 1.013 ms - !!
Der einzige Index, den Sie benötigen, ist der von Ihnen erstellte : station_id__submitted_at
. Die UNIQUE
Einschränkung uniq_sid_sat
erledigt im Grunde auch die Arbeit. Beides beizubehalten scheint eine Verschwendung von Speicherplatz und Schreibleistung zu sein.
Ich NULLS LAST
habe ORDER BY
in der Abfrage hinzugefügt , weil submitted_at
nicht definiert ist NOT NULL
. Fügen Sie NOT NULL
der Spalte gegebenenfalls eine Einschränkung hinzu submitted_at
, löschen Sie den zusätzlichen Index und entfernen Sie ihn NULLS LAST
aus der Abfrage.
Wenn submitted_at
möglich NULL
, erstellen Sie diesen UNIQUE
Index, um sowohl Ihren aktuellen Index als auch die eindeutige Einschränkung zu ersetzen :
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Erwägen:
Dies setzt eine separate Tabellestation
mit einer Zeile pro relevanter station_id
(normalerweise der PK) voraus - die Sie so oder so haben sollten. Wenn Sie es nicht haben, erstellen Sie es. Wieder sehr schnell mit dieser rCTE-Technik:
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;
Ich benutze das auch in der Geige. Sie können eine ähnliche Abfrage verwenden, um Ihre Aufgabe direkt ohne station
Tabelle zu lösen - wenn Sie nicht überzeugt sind, sie zu erstellen.
Detaillierte Anweisungen, Erklärungen und Alternativen:
Index optimieren
Ihre Anfrage sollte jetzt sehr schnell sein. Nur wenn Sie die Leseleistung noch optimieren müssen ...
Es kann sinnvoll sein, level_sensor
als letzte Spalte zum Index hinzuzufügen , um nur Index-Scans zu ermöglichen , wie von joanolo kommentiert .
Con: Dadurch wird der Index größer - was für alle Abfragen, die ihn verwenden, ein wenig Kosten verursacht.
Pro: Wenn Sie tatsächlich nur Index-Scans erhalten, muss die vorliegende Abfrage überhaupt keine Heap-Seiten besuchen, was sie etwa doppelt so schnell macht. Aber das kann jetzt ein unwesentlicher Gewinn für die sehr schnelle Abfrage sein.
Allerdings erwarte ich nicht , dass für Ihren Fall an der Arbeit. Du erwähntest:
... ungefähr 20.000 Zeilen pro Tag pro station_id
.
In der Regel bedeutet dies eine unaufhörliche Schreiblast (1 pro station_id
5 Sekunden). Und Sie interessieren sich für die neueste Reihe. Nur-Index-Scans funktionieren nur für Heap-Seiten, die für alle Transaktionen sichtbar sind (das Bit in der Sichtbarkeitskarte ist gesetzt). Sie müssten extrem aggressive VACUUM
Einstellungen für die Tabelle vornehmen, um mit der Schreiblast Schritt zu halten, und es würde die meiste Zeit immer noch nicht funktionieren. Wenn meine Annahmen richtig sind, sind nur Index-Scans nicht verfügbar level_sensor
. Fügen Sie sie nicht zum Index hinzu.
OTOH, wenn meine Annahmen zutreffen und Ihre Tabelle sehr groß wird , könnte ein BRIN-Index helfen. Verbunden:
Oder noch spezialisierter und effizienter: Ein Teilindex nur für die neuesten Ergänzungen, um den Großteil der irrelevanten Zeilen abzuschneiden:
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';
Wählen Sie einen Zeitstempel, für den Sie wissen, dass jüngere Zeilen vorhanden sein müssen. Sie müssen WHERE
allen Abfragen eine übereinstimmende Bedingung hinzufügen , z.
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Sie müssen Index und Abfrage von Zeit zu Zeit anpassen.
Verwandte Antworten mit mehr Details: