Tengo una tabla station_logs
en 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_sensor
valor basado en submitted_at
, para cada uno station_id
. Hay alrededor de 400 station_id
valores ú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 ANALYZE
según lo dispuesto por el OP:El único índice que necesita es la que ha creado:
station_id__submitted_at
. LaUNIQUE
restricciónuniq_sid_sat
también hace el trabajo, básicamente. Mantener ambos parece una pérdida de espacio en disco y rendimiento de escritura.Agregué
NULLS LAST
aORDER BY
en la consulta porquesubmitted_at
no está definidoNOT NULL
. Idealmente, si corresponde, agregue unaNOT NULL
restricción a la columnasubmitted_at
, suelte el índice adicional y elimíneloNULLS LAST
de la consulta.Si
submitted_at
puedeNULL
, cree esteUNIQUE
índice para reemplazar tanto su índice actual como su restricción única:Considerar:
Esto supone una tabla separada
station
con 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
station
tabla, 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_sensor
como ú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_id
cada 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 ejecutarVACUUM
configuraciones 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_sensor
al í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
WHERE
condició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