Hacer esta pregunta, específicamente para Postgres, ya que tiene un buen supoort para los índices R-tree / espacial.
Tenemos la siguiente tabla con una estructura de árbol (modelo de conjunto anidado) de palabras y sus frecuencias:
lexikon
-------
_id integer PRIMARY KEY
word text
frequency integer
lset integer UNIQUE KEY
rset integer UNIQUE KEY
Y la consulta:
SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N
Supongo que (lset, frequency, word)
sería útil un índice de cobertura, pero creo que puede no funcionar bien si hay demasiados lset
valores en el (@High, @Low)
rango.
Un índice simple en (frequency DESC)
también puede ser suficiente a veces, cuando una búsqueda que usa ese índice produce temprano las @N
filas que coinciden con la condición de rango.
Pero parece que el rendimiento depende mucho de los valores de los parámetros.
¿Hay alguna manera de hacer que funcione rápidamente, independientemente de si el rango (@Low, @High)
es ancho o estrecho y si las palabras de frecuencia superior están afortunadamente en el rango seleccionado (estrecho)?
¿Ayudaría un árbol R / índice espacial?
Agregar índices, reescribir la consulta, rediseñar la tabla, no hay limitación.
fuente
lset,rset
yword
.Respuestas:
Puede lograr un mejor rendimiento buscando primero en filas con frecuencias más altas. Esto se puede lograr 'granulando' las frecuencias y luego avanzando por ellas de manera procesal, por ejemplo, de la siguiente manera:
--testbed y
lexikon
datos ficticios:granule
análisis (principalmente para información y ajuste):función para escanear frecuencias altas primero:
resultados (los tiempos probablemente deberían tomarse con una pizca de sal, pero cada consulta se ejecuta dos veces para contrarrestar cualquier almacenamiento en caché)
primero usando la función que hemos escrito:
y luego con un escaneo de índice simple:
Dependiendo de sus datos del mundo real, es probable que desee variar la cantidad de gránulos y la función utilizada para colocar filas en ellos. La distribución real de frecuencias es clave aquí, al igual que los valores esperados para la
limit
cláusula y el tamaño de loslset
rangos buscados.fuente
width_granule=8
entregranulae_start
ygranulae_end
del nivel anterior?frequency
se genera: una gran brecha entre 1e6 / 2 y 1e6 / 3, cuanto mayor es el número de fila, menor es la brecha. De todos modos, ¡Gracias por este increíble enfoque!Preparar
Estoy construyendo sobre la configuración de @ Jack para que sea más fácil para las personas seguir y comparar. Probado con PostgreSQL 9.1.4 .
De aquí en adelante tomo una ruta diferente:
Mesa auxiliar
Esta solución no agrega columnas a la tabla original, solo necesita una pequeña tabla auxiliar. Lo puse en el esquema
public
, use cualquier esquema de su elección.La tabla se ve así:
Como la columna
cond
se va a usar en SQL dinámico más abajo, debe hacer que esta tabla sea segura . Siempre califique el esquema de la tabla si no puede estar seguro de una corriente apropiadasearch_path
y revoque los privilegios de escritura depublic
(y cualquier otra función no confiable):La tabla
lex_freq
tiene tres propósitos:Índices
Esta
DO
declaración crea todos los índices necesarios:Todos estos índices parciales juntos abarcan la tabla una vez. Son aproximadamente del mismo tamaño que un índice básico en toda la tabla:
Solo 21 MB de índices para una tabla de 50 MB hasta ahora.
Creo la mayoría de los índices parciales en
(lset, frequency DESC)
. La segunda columna solo ayuda en casos especiales. Pero como ambas columnas involucradas son de tipointeger
, debido a los detalles de la alineación de datos en combinación con MAXALIGN en PostgreSQL, la segunda columna no hace que el índice sea más grande. Es una pequeña victoria por casi ningún costo.No tiene sentido hacerlo para índices parciales que solo abarcan una sola frecuencia. Esos son sólo en
(lset)
. Los índices creados se ven así:Función
La función es algo similar en estilo a la solución de @ Jack:
Diferencias clave
SQL dinámico con
RETURN QUERY EXECUTE
.A medida que avanzamos por los pasos, un plan de consulta diferente puede ser beneficiario. El plan de consulta para SQL estático se genera una vez y luego se reutiliza, lo que puede ahorrar algo de sobrecarga. Pero en este caso la consulta es simple y los valores son muy diferentes. El SQL dinámico será una gran victoria.
Dinámico
LIMIT
para cada paso de consulta.Esto ayuda de varias maneras: primero, las filas solo se obtienen según sea necesario. En combinación con SQL dinámico, esto también puede generar diferentes planes de consulta para empezar. Segundo: no se necesita una
LIMIT
llamada adicional en la función para recortar el excedente.Punto de referencia
Preparar
Elegí cuatro ejemplos y realicé tres pruebas diferentes con cada uno. Tomé el mejor de cinco para comparar con el caché cálido:
La consulta SQL sin formato del formulario:
Lo mismo después de crear este índice.
Necesita aproximadamente el mismo espacio que todos mis índices parciales juntos:
La función
Resultados
1: Tiempo de ejecución total: 315.458 ms
2: Tiempo de ejecución total: 36.458 ms
3: Tiempo de ejecución total: 0.330 ms
1: Tiempo de ejecución total: 294.819 ms
2: Tiempo de ejecución total: 18.915 ms
3: Tiempo de ejecución total: 1.414 ms
1: Tiempo de ejecución total: 426.831 ms
2: Tiempo de ejecución total: 217.874 ms
3: Tiempo de ejecución total: 1.611 ms
1: Tiempo de ejecución total: 2458.205 ms
2: Tiempo de ejecución total: 2458.205 ms - para grandes rangos de lset, la exploración seq es más rápida que el índice.
3: Tiempo de ejecución total: 0.266 ms
Conclusión
Como se esperaba, el beneficio de la función crece con rangos más grandes
lset
y más pequeñosLIMIT
.Con rangos muy pequeños de
lset
, la consulta sin procesar en combinación con el índice es en realidad más rápida . Querrá probar y quizás ramificar: consulta sin procesar para pequeños rangos delset
, de lo contrario, llamar a la función. Incluso podría incorporar eso en la función para "lo mejor de ambos mundos", eso es lo que haría.Dependiendo de su distribución de datos y consultas típicas, más pasos
lex_freq
pueden ayudar al rendimiento. Prueba para encontrar el punto ideal. Con las herramientas presentadas aquí, debería ser fácil de probar.fuente
No veo ningún motivo para incluir la palabra columna en el índice. Entonces este índice
hará que su consulta se realice rápidamente.
UPD
Actualmente no hay formas de hacer un índice de cobertura en PostgreSQL. Hubo una discusión sobre esta característica en la lista de correo de PostgreSQL http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php
fuente
Usando un índice GIST
Depende de lo que quieras decir cuando ayunas: obviamente tienes que visitar cada fila del rango porque tu consulta es
ORDER freq DESC
. Tímido de eso, el planificador de consultas ya cubre esto si entiendo la pregunta,Aquí creamos una tabla con 10k filas de
(5::int,random()::double precision)
Lo indexamos,
Lo consultamos
Tenemos a
Seq Scan on t
. Esto se debe simplemente a que nuestras estimaciones de selectividad permiten a pg concluir que el acceso al almacenamiento dinámico es más rápido que escanear un índice y volver a verificar. Así que lo hacemos más jugoso insertando 1,000,000 filas más(42::int,random()::double precision)
que no se ajustan a nuestro "rango".Y luego preguntamos,
Se puede ver aquí completamos en 4,6 ms con un índice sólo Scan ,
Ampliar el rango para incluir toda la tabla, produce otra exploración secuencial, lógicamente, y crecerla con otros mil millones de filas produciría otra Exploración de índice.
En resumen,
fuente