Para Postgres 9.1 o posterior:
CREATE INDEX idx_time_limits_ts_inverse
ON time_limits (id_phi, start_date_time, end_date_time DESC);
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:
SELECT *
FROM time_limits
WHERE id_phi = 0
AND start_date_time <= '2010-08-08 00:00'
AND end_date_time >= '2010-08-08 00:05';
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 :
- Desea
start_date_time <= something
: index tiene la marca de tiempo más temprana primero.
- Si califica, también verifique la columna 3.
Repita hasta que la primera fila no califique (superrápido).
- Desea
end_date_time >= something
: index tiene la última marca de tiempo primero.
- Si califica, sigue buscando filas hasta que la primera no (súper rápido).
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 yORDER 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 con id_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 ...
ALTER TABLE time_limits ALTER start_date_time SET STATISTICS 1000;
ALTER TABLE time_limits ALTER end_date_time SET STATISTICS 1000;
... 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_repack
Si 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:
ALTER TABLE time_limits CLUSTER ON idx_time_limits_inversed;
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
y timestamp with time zone
: tsrange
,tstzrange
. Un índice btree suele ser más rápido para una integer
columna adicional como id_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 un integer
.
La trifecta! Un índice GiST funcional de varias columnas :
CREATE EXTENSION IF NOT EXISTS btree_gist; -- if not installed, yet
CREATE INDEX idx_time_limits_funky ON time_limits USING gist
(id_phi, tsrange(start_date_time, end_date_time, '[]'));
Utilice el operador "contiene rango"@>
en su consulta ahora:
SELECT *
FROM time_limits
WHERE id_phi = 0
AND tsrange(start_date_time, end_date_time, '[]')
@> tsrange('2010-08-08 00:00', '2010-08-08 00:05', '[]')
Í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 :
Actualmente, solo los tipos de índice B-tree, GiST, GIN y BRIN admiten índices de varias columnas.
Sigue siendo cierto en Postgres 12.
Tendría que combinar un spgist
índice solo (tsrange(...))
con un segundo btree
í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:
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)