Tengo una tabla station_logsen una base de datos PostgreSQL 9.6:
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)
Estoy tratando de obtener el último level_sensorvalor basado en submitted_at, para cada uno station_id. Hay alrededor de 400 station_idvalores únicos y alrededor de 20k filas por día por station_id.
Antes de crear el índice:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Único (costo = 4347852.14..4450301.72 filas = 89 ancho = 20) (tiempo real = 22202.080..27619.167 filas = 98 bucles = 1)
-> Ordenar (costo = 4347852.14..4399076.93 filas = 20489916 ancho = 20) (tiempo real = 22202.077..26540.827 filas = 20489812 bucles = 1)
Clave de clasificación: station_id, submit_at DESC
Método de clasificación: disco de fusión externo: 681040kB
-> Seq Scan en station_logs (costo = 0.00..598895.16 filas = 20489916 ancho = 20) (tiempo real = 0.023..3443.587 filas = 20489812 bucles = $
Tiempo de planificación: 0.072 ms
Tiempo de ejecución: 27690.644 ms
Creando índice:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Después de crear el índice, para la misma consulta:
Único (costo = 0.56..2156367.51 filas = 89 ancho = 20) (tiempo real = 0.184..16263.413 filas = 98 bucles = 1) -> Escaneo de índice usando station_id__submitted_at en station_logs (costo = 0.56..2105142.98 filas = 20489812 ancho = 20) (tiempo real = 0.181..1 $ Tiempo de planificación: 0.206 ms Tiempo de ejecución: 16263.490 ms
¿Hay alguna manera de hacer esta consulta más rápido? Como 1 segundo, por ejemplo, 16 segundos todavía es demasiado.

Respuestas:
Para solo 400 estaciones, esta consulta será masivamente más rápida:
dbfiddle aquí
(comparando planes para esta consulta, la alternativa de Abelisto y su original)
Resultando
EXPLAIN ANALYZEsegún lo dispuesto por el OP:Bucle anidado (costo = 0.56..356.65 filas = 102 ancho = 20) (tiempo real = 0.034..0.979 filas = 98 bucles = 1) -> Seq Scan en estaciones s (costo = 0.00..3.02 filas = 102 ancho = 4) (tiempo real = 0.009..0.016 filas = 102 bucles = 1) -> Límite (costo = 0.56..3.45 filas = 1 ancho = 16) (tiempo real = 0.009..0.009 filas = 1 bucles = 102) -> Escaneo de índice usando station_id__submitted_at en station_logs (costo = 0.56..664062.38 filas = 230223 ancho = 16) (tiempo real = 0.009 $ Índice Cond: (station_id = s.id) Tiempo de planificación: 0.542 ms Tiempo de ejecución: 1.013 ms - !!El único índice que necesita es la que ha creado:
station_id__submitted_at. LaUNIQUErestricciónuniq_sid_sattambién hace el trabajo, básicamente. Mantener ambos parece una pérdida de espacio en disco y rendimiento de escritura.Agregué
NULLS LASTaORDER BYen la consulta porquesubmitted_atno está definidoNOT NULL. Idealmente, si corresponde, agregue unaNOT NULLrestricción a la columnasubmitted_at, suelte el índice adicional y elimíneloNULLS LASTde la consulta.Si
submitted_atpuedeNULL, cree esteUNIQUEíndice para reemplazar tanto su índice actual como su restricción única:Considerar:
Esto supone una tabla separada
stationcon una fila por relevantestation_id(generalmente el PK), que debería tener en cualquier caso. Si no lo tiene, créelo. Nuevamente, muy rápido con esta técnica rCTE:Lo uso en el violín también. Puede utilizar una consulta similar para resolver su tarea directamente, sin
stationtabla, si no puede convencerse de crearla.Instrucciones detalladas, explicación y alternativas:
Optimizar índice
Su consulta debería ser muy rápida ahora. Solo si aún necesita optimizar el rendimiento de lectura ...
Puede tener sentido agregar
level_sensorcomo última columna al índice para permitir escaneos de solo índice , como comentó joanolo .Con: hace que el índice sea más grande, lo que agrega un pequeño costo a todas las consultas que lo usan.
Pro: si realmente obtiene escaneos de índice, la consulta en cuestión no tiene que visitar páginas de montón, lo que hace que sea aproximadamente el doble de rápido. Pero eso puede ser una ganancia insustancial para la consulta muy rápida ahora.
Sin embargo , no espero que eso funcione para su caso. Mencionaste:
Típicamente, eso indicaría una carga de escritura incesante (1 por
station_idcada 5 segundos). Y estás interesado en la última fila. Los escaneos de solo índice solo funcionan para páginas de montón que son visibles para todas las transacciones (se establece el bit en el mapa de visibilidad). Tendría que ejecutarVACUUMconfiguraciones extremadamente agresivas para que la tabla se mantuviera al día con la carga de escritura, y aún así no funcionaría la mayor parte del tiempo. Si mis suposiciones son correctas, los escaneos de solo índice están fuera, no agreguelevel_sensoral índice.OTOH, si mis suposiciones se mantienen y su tabla está creciendo muy grande , un índice BRIN podría ayudar. Relacionado:
O, incluso más especializado y más eficiente: un índice parcial de solo las últimas incorporaciones para cortar la mayor parte de las filas irrelevantes:
Elija una marca de tiempo para la que sepa que deben existir filas más jóvenes. Debe agregar una
WHEREcondición coincidente a todas las consultas, como:Debe adaptar el índice y la consulta de vez en cuando.
Respuestas relacionadas con más detalles:
fuente
Prueba la forma clásica:
dbfiddle
EXPLICAR ANÁLISIS por ThreadStarter
fuente