Ich habe eine Tabelle station_logs
in einer PostgreSQL 9.6-Datenbank:
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)
Ich versuche , für jeden den letzten level_sensor
Wert zu ermitteln . Es gibt ungefähr 400 eindeutige Werte und ungefähr 20.000 Zeilen pro Tag und Tag .submitted_at
station_id
station_id
station_id
Vor dem Erstellen eines Index:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Einzigartig (Kosten = 4347852.14..4450301.72 Zeilen = 89 Breite = 20) (tatsächliche Zeit = 22202.080..27619.167 Zeilen = 98 Schleifen = 1) -> Sortieren (Kosten = 4347852.14..4399076.93 Zeilen = 20489916 Breite = 20) (tatsächliche Zeit = 22202.077..26540.827 Zeilen = 20489812 Schleifen = 1) Sortierschlüssel: station_id, submit_at DESC Sortiermethode: Externe Zusammenführung Datenträger: 681040kB -> Seq Scan on station_logs (Kosten = 0,00..598895.16 Zeilen = 20489916 Breite = 20) (tatsächliche Zeit = 0.023..3443.587 Zeilen = 20489812 Schleifen = $ Planungszeit: 0,072 ms Ausführungszeit: 27690.644 ms
Index erstellen:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Nach dem Erstellen des Index für dieselbe Abfrage:
Einzigartig (Kosten = 0,56..2156367.51 Zeilen = 89 Breite = 20) (tatsächliche Zeit = 0.184..16263.413 Zeilen = 98 Schleifen = 1) -> Index-Scan mit station_id__submitted_at in station_logs (Kosten = 0,56..2105142.98 Zeilen = 20489812 Breite = 20) (tatsächliche Zeit = 0.181..1 $ Planungszeit: 0,206 ms Ausführungszeit: 16263.490 ms
Gibt es eine Möglichkeit, diese Abfrage zu beschleunigen? Wie zum Beispiel 1 Sekunde sind 16 Sekunden immer noch zu viel.
Antworten:
Bei nur 400 Stationen ist diese Abfrage erheblich schneller:
dbfiddle hier
(Vergleich der Pläne für diese Abfrage, Abelistos Alternative und Ihr Original)
Ergebnis
EXPLAIN ANALYZE
wie vom OP bereitgestellt:Der einzige Index, den Sie benötigen, ist der von Ihnen erstellte :
station_id__submitted_at
. DieUNIQUE
Einschränkunguniq_sid_sat
erledigt im Grunde auch die Arbeit. Beides beizubehalten scheint eine Verschwendung von Speicherplatz und Schreibleistung zu sein.Ich
NULLS LAST
habeORDER BY
in der Abfrage hinzugefügt , weilsubmitted_at
nicht definiert istNOT NULL
. Fügen SieNOT NULL
der Spalte gegebenenfalls eine Einschränkung hinzusubmitted_at
, löschen Sie den zusätzlichen Index und entfernen Sie ihnNULLS LAST
aus der Abfrage.Wenn
submitted_at
möglichNULL
, erstellen Sie diesenUNIQUE
Index, um sowohl Ihren aktuellen Index als auch die eindeutige Einschränkung zu ersetzen :Erwägen:
Dies setzt eine separate Tabelle
station
mit einer Zeile pro relevanterstation_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: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:
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 aggressiveVACUUM
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ügbarlevel_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:
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.Sie müssen Index und Abfrage von Zeit zu Zeit anpassen.
Verwandte Antworten mit mehr Details:
quelle
Probieren Sie den klassischen Weg:
dbfiddle
EXPLAIN ANALYZE von ThreadStarter
quelle