Solo per 400 stazioni, questa query sarà enormemente più veloce:
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 qui
(confrontando i piani per questa query, l'alternativa di Abelisto e l'originale)
Risulta EXPLAIN ANALYZE
come previsto dal PO:
Loop nidificato (costo = 0,56..356,65 righe = 102 larghezza = 20) (tempo effettivo = 0,034..0,979 righe = 98 loop = 1)
-> Scansione Seq su stazioni s (costo = 0,00..3,02 righe = 102 larghezza = 4) (tempo effettivo = 0,009..0,016 righe = 102 loop = 1)
-> Limite (costo = 0,56..3,45 righe = 1 larghezza = 16) (tempo effettivo = 0,009..0,009 righe = 1 loop = 102)
-> Scansione indice utilizzando station_id__submitted_at su station_logs (costo = 0,56..664062,38 righe = 230223 larghezza = 16) (tempo effettivo = 0,009 $
Indice cond: (station_id = s.id)
Tempo di pianificazione: 0,542 ms
Tempo di esecuzione: 1.013 ms - !!
L'unico indice di cui hai bisogno è quello che si è creato: station_id__submitted_at
. Il UNIQUE
vincolo uniq_sid_sat
fa anche il lavoro, in pratica. Mantenere entrambi sembra uno spreco di spazio su disco e prestazioni di scrittura.
Ho aggiunto NULLS LAST
a ORDER BY
nella query perché submitted_at
non è definito NOT NULL
. Idealmente, se applicabile !, aggiungere un NOT NULL
vincolo alla colonna submitted_at
, eliminare l'indice aggiuntivo e rimuoverlo NULLS LAST
dalla query.
Se submitted_at
può essere NULL
, creare questa UNIQUE
indice per sostituire sia l'indice corrente e vincolo univoco:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Ritenere:
Ciò presuppone una tabella separatastation
con una riga per pertinente station_id
(in genere il PK) - che dovresti avere in entrambi i modi. Se non lo hai, crealo. Ancora una volta, molto velocemente con questa tecnica 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;
Lo uso anche nel violino. È possibile utilizzare una query simile per risolvere l'attività direttamente, senza station
tabella, se non si è convinti di crearla.
Istruzioni dettagliate, spiegazioni e alternative:
Ottimizza l'indice
La tua query dovrebbe essere molto veloce ora. Solo se hai ancora bisogno di ottimizzare le prestazioni di lettura ...
Potrebbe avere senso aggiungere level_sensor
l'ultima colonna all'indice per consentire scansioni solo dell'indice , come commentato joanolo .
Contro: rende l'indice più grande, il che aggiunge un piccolo costo a tutte le query che lo utilizzano.
Pro: se in realtà ottieni solo scansioni dell'indice, la query a portata di mano non deve assolutamente visitare le pagine heap, il che lo rende circa il doppio più veloce. Ma questo potrebbe essere un guadagno non sostanziale per la query molto veloce ora.
Tuttavia , non mi aspetto che funzioni per il tuo caso. Hai nominato:
... circa 20.000 file al giorno per station_id
.
In genere, ciò indica un carico di scrittura incessante (1 station_id
ogni 5 secondi). E sei interessato all'ultima riga. Le scansioni solo indice funzionano solo per le pagine heap che sono visibili a tutte le transazioni (il bit nella mappa di visibilità è impostato). Dovresti eseguire VACUUM
impostazioni estremamente aggressive per la tabella per tenere il passo con il carico di scrittura e non funzionerebbe ancora per la maggior parte del tempo. Se i miei presupposti sono corretti, le scansioni solo indice sono fuori, non aggiungere level_sensor
all'indice.
OTOH, se le mie ipotesi valgono e il tuo tavolo sta diventando molto grande , un indice BRIN potrebbe aiutare. Relazionato:
O ancora più specializzato e più efficiente: un indice parziale solo per le ultime aggiunte per tagliare la maggior parte delle righe irrilevanti:
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';
Scegli un timestamp per il quale sai che devono esistere righe più giovani. Devi aggiungere una WHERE
condizione corrispondente a tutte le query, come:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
È necessario adattare periodicamente l'indice e la query.
Risposte correlate con maggiori dettagli: