Yo uso PostgreSQL 9.1 en Ubuntu 12.04.
Necesito seleccionar registros dentro de un rango de tiempo: mi tabla time_limits
tiene dos timestamp
campos y una integer
propiedad. Hay columnas adicionales en mi tabla real que no están involucradas con esta consulta.
create table (
start_date_time timestamp,
end_date_time timestamp,
id_phi integer,
primary key(start_date_time, end_date_time,id_phi);
Esta tabla contiene aproximadamente 2 millones de registros.
Consultas como las siguientes tomaron enormes cantidades de tiempo:
select * from time_limits as t
where t.id_phi=0
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time >= timestamp'2010-08-08 00:05:00';
Así que intenté agregar otro índice, el inverso de la PK:
create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);
Tengo la impresión de que el rendimiento mejoró: el tiempo para acceder a los registros en el medio de la tabla parece ser más razonable: entre 40 y 90 segundos.
Pero todavía son varias decenas de segundos para valores en el medio del rango de tiempo. Y dos veces más al apuntar al final de la tabla (cronológicamente hablando).
Intenté explain analyze
por primera vez obtener este plan de consulta:
Bitmap Heap Scan on time_limits (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
-> Bitmap Index Scan on idx_time_limits_phi_start_end (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
Total runtime: 44.507 ms
Ver los resultados en depesz.com.
¿Qué puedo hacer para optimizar la búsqueda? Puede ver todo el tiempo dedicado a escanear las dos columnas de marcas de tiempo una vez que id_phi
se establece en 0
. Y no entiendo el gran escaneo (¡60K filas!) En las marcas de tiempo. ¿No están indexados por la clave principal y idx_inversed
agregué?
¿Debo cambiar de tipos de marca de tiempo a otra cosa?
He leído un poco sobre los índices GIST y GIN. Supongo que pueden ser más eficientes en ciertas condiciones para tipos personalizados. ¿Es una opción viable para mi caso de uso?
fuente
explain analyze
salida es el tiempo que la consulta necesitó en el servidor . Si su consulta tarda 45 segundos, el tiempo adicional se gasta transfiriendo los datos de la base de datos al programa que ejecuta la consulta Después de todo, son 62682 filas y si cada fila es grande (por ejemplo, largavarchar
otext
columnas), esto puede afectar el tiempo de transferencia drásticamenterows=62682 rows
es la estimación del planificador . La consulta devuelve 0 filas.(actual time=44.446..44.446 rows=0 loops=1)
Respuestas:
Para Postgres 9.1 o posterior:
En la mayoría de los casos, el orden de clasificación de un índice apenas es relevante. Postgres puede escanear hacia atrás prácticamente tan rápido. Pero para las consultas de rango en varias columnas, puede hacer una gran diferencia. Estrechamente relacionada:
Considere su consulta:
El orden de clasificación de la primera columna
id_phi
del índice es irrelevante. Como se verifica la igualdad (=
), debería ser lo primero. Tienes razón. Más en esta respuesta relacionada:Postgres puede saltar
id_phi = 0
en muy poco tiempo y considerar las siguientes dos columnas del índice coincidente. Estos se consultan con condiciones de rango de orden inverso (<=
,>=
). En mi índice, las filas de calificación son lo primero. Debería ser la forma más rápida posible con un índice B-Tree 1 :start_date_time <= something
: index tiene la marca de tiempo más temprana primero.Repita hasta que la primera fila no califique (superrápido).
end_date_time >= something
: index tiene la última marca de tiempo primero.Continúe con el siguiente valor para la columna 2 ..
Postgres puede escanear hacia adelante o hacia atrás. De la forma en que tenía el índice, tiene que leer todas las filas que coinciden en las dos primeras columnas y luego filtrar en la tercera. Asegúrese de leer el capítulo Índices y
ORDER BY
el manual. Se ajusta bastante bien a tu pregunta.¿Cuántas filas coinciden en las dos primeras columnas?
Solo unos pocos con un
start_date_time
inicio cercano al rango de tiempo de la tabla. ¡Pero casi todas las filas conid_phi = 0
en el extremo cronológico de la tabla! Por lo tanto, el rendimiento se deteriora con tiempos de inicio posteriores.Estimaciones del planificador
El planificador estima
rows=62682
su consulta de ejemplo. De ellos, ninguno califica (rows=0
). Puede obtener mejores estimaciones si aumenta el objetivo de estadísticas para la tabla. Para 2.000.000 de filas ...... podría pagar. O incluso más alto. Más en esta respuesta relacionada:
Supongo que no necesita eso para
id_phi
(solo unos pocos valores distintos, distribuidos uniformemente), sino para las marcas de tiempo (muchos valores distintos, distribuidos de manera desigual).Tampoco creo que importe mucho con el índice mejorado.
CLUSTER
/ pg_repackSi lo desea más rápido, puede optimizar el orden físico de las filas en su tabla. Si puede permitirse bloquear su mesa exclusivamente durante un corto período de tiempo (por ejemplo, fuera de horario) para reescribir su mesa y ordenar las filas de acuerdo con el índice:
Con acceso concurrente, considere pg_repack , que puede hacer lo mismo sin bloqueo exclusivo.
De cualquier manera, el efecto es que es necesario leer menos bloques de la tabla y todo está ordenado previamente. Es un efecto de una sola vez que se deteriora con el tiempo con escrituras en la tabla que fragmentan el orden físico.
Índice GiST en Postgres 9.2+
1 Con la página 9.2+ hay otra opción, posiblemente más rápida: un índice GiST para una columna de rango.
Hay tipos de rango integrados para
timestamp
ytimestamp with time zone
:tsrange
,tstzrange
. Un índice btree suele ser más rápido para unainteger
columna adicional comoid_phi
. Más pequeño y más barato de mantener, también. Pero la consulta probablemente seguirá siendo más rápida en general con el índice combinado.Cambie la definición de su tabla o use un índice de expresión .
Para el índice GiST multicolumna disponible, también necesita el módulo adicional
btree_gist
instalado (una vez por base de datos) que proporciona las clases de operador para incluir uninteger
.La trifecta! Un índice GiST funcional de varias columnas :
Utilice el operador "contiene rango"
@>
en su consulta ahora:Índice SP-GiST en Postgres 9.3+
Un índice SP-GiST podría ser aún más rápido para este tipo de consulta, excepto que, citando el manual :
Sigue siendo cierto en Postgres 12.
Tendría que combinar un
spgist
índice solo(tsrange(...))
con un segundobtree
índice encendido(id_phi)
. Con la sobrecarga adicional, no estoy seguro de que esto pueda competir.Respuesta relacionada con un punto de referencia para solo una
tsrange
columna:fuente
La respuesta de Erwin ya es exhaustiva, sin embargo:
Los tipos de rango para marcas de tiempo están disponibles en PostgreSQL 9.1 con la extensión Temporal de Jeff Davis: https://github.com/jeff-davis/PostgreSQL-Temporal
Nota: tiene funciones limitadas (usa Timestamptz, y solo puede tener el estilo '[)' superpuesto afaik). Además, hay muchas otras buenas razones para actualizar a PostgreSQL 9.2.
fuente
Podría intentar crear el índice de varias columnas en un orden diferente:
Publiqué una vez una pregunta similar también relacionada con el orden de los índices en un índice de varias columnas. La clave es tratar de usar primero las condiciones más restrictivas para reducir el espacio de búsqueda.
Editar : Mi error. Ahora veo que ya tienes este índice definido.
fuente
Bitmap Index Scan on idx_time_limits_phi_start_end
Logré aumentar rápidamente (de 1 segundo a 70 ms)
Tengo una tabla con agregaciones de muchas medidas y muchos niveles (
l
columna) (30s, 1m, 1h, etc.), hay dos columnas de rango:$s
para el inicio y$e
para el final.Creé dos índices de varias columnas: uno para inicio y otro para final.
Ajusté la consulta de selección: seleccione los rangos donde su límite inicial está en el rango dado. además, seleccione rangos donde su límite final esté en un rango dado.
Explain muestra dos flujos de filas utilizando nuestros índices de manera eficiente.
Índices:
Seleccionar consulta:
Explique:
El truco es que los nodos de su plan contienen solo las filas deseadas. Anteriormente obtuvimos miles de filas en el nodo del plan porque seleccionó
all points from some point in time to the very end
, luego el siguiente nodo eliminó las filas innecesarias.fuente