En una base de datos Postgres 9.1, tengo una tabla table1
con ~ 1.5M filas y una columna label
(nombres simplificados por el bien de esta pregunta).
Hay un funcional trigrama-índice en lower(unaccent(label))
( unaccent()
se ha hecho inmutable para permitir su uso en el índice).
La siguiente consulta es bastante rápida:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
count
-------
1
(1 row)
Time: 394,295 ms
Pero la siguiente consulta es más lenta:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
count
-------
1
(1 row)
Time: 1405,749 ms
Y añadiendo más palabras es aún más lenta, a pesar de que la búsqueda es más estricta.
Intenté un simple truco para ejecutar una subconsulta para la primera palabra y luego una consulta con la cadena de búsqueda completa, pero (tristemente) el planificador de consultas vio a través de mis maquinaciones:
EXPLAIN ANALYZE
SELECT * FROM (
SELECT id, title, label from table1
WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
Bitmap Heap Scan en la tabla1 (costo = 16216.01..16220.04 filas = 1 ancho = 212) (tiempo real = 1824.017..1824.019 filas = 1 bucles = 1) Vuelva a verificar Cond: ((lower (unaccent ((label) :: text)) ~~ '% someword%' :: text) AND (lower (unaccent ((label) :: text)) ~~ '% someword y algo más %'::texto)) -> Escaneo de índice de mapa de bits en table1_label_hun_gin_trgm (costo = 0.00..16216.01 filas = 1 ancho = 0) (tiempo real = 1823.900..1823.900 filas = 1 bucles = 1) Índice Cond: ((lower (unaccent ((label) :: text)) ~~ '% someword%' :: text) AND (lower (unaccent ((label) :: text)) ~~ '% someword y algo más %'::texto)) Tiempo de ejecución total: 1824.064 ms
Mi problema principal es que la cadena de búsqueda proviene de una interfaz web que puede enviar cadenas bastante largo y por lo tanto ser muy lento y también puede constituir un vector de DOS.
Entonces mis preguntas son:
- ¿Cómo acelerar la consulta?
- ¿Hay una manera de dividirlo en sub consultas por lo que es más rápido?
- ¿Quizás una versión posterior de Postgres es mejor? (Intenté 9.4 y no parece más rápido: sigue siendo el mismo efecto. ¿Quizás una versión posterior?)
- ¿Quizás se necesita una estrategia de indexación diferente?
unaccent()
también es proporcionado por un módulo adicional y Postgres no admite índices en la función de forma predeterminada, ya que no lo esIMMUTABLE
. Debe haber alterado algo y debe mencionar lo que hizo exactamente en su pregunta. Mi consejo permanente: stackoverflow.com/a/11007216/939860 . Además, los índices de trigram admiten compatibilidad sin distinción entre mayúsculas y minúsculas. Se puede simplificar a:WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')
- con un índice coincidente. Detalles: stackoverflow.com/a/28636000/939860 .unaccent
inmutable. Agregué esto a la pregunta.unaccent
módulo. Una de las razones por las que sugiero un envoltorio de funciones en su lugar.Respuestas:
En PostgreSQL 9.6 habrá una nueva versión de pg_trgm, 1.2, que será mucho mejor al respecto. Con un poco de esfuerzo, también puede hacer que esta nueva versión funcione en PostgreSQL 9.4 (debe aplicar el parche, compilar el módulo de extensión usted mismo e instalarlo).
Lo que hace la versión más antigua es buscar cada trigrama en la consulta y tomar la unión de ellos, y luego aplicar un filtro. Lo que hará la nueva versión es elegir el trigrama más raro en la consulta y buscar solo ese, y luego filtrar el resto más tarde.
La maquinaria para hacer esto no existe en 9.1. En 9.4 esa maquinaria fue agregada, pero pg_trgm no estaba adaptada para usarla en ese momento.
Aún tendría un posible problema de DOS, ya que la persona malintencionada puede elaborar una consulta que solo tenga trigramas comunes. como '% y%', o incluso '% a%'
Si no puede actualizar a pg_trgm 1.2, entonces otra forma de engañar al planificador sería:
Al concatenar la cadena vacía para etiquetar, engaña al planificador para que piense que no puede usar el índice en esa parte de la cláusula where. Por lo tanto, utiliza el índice solo en% someword% y aplica un filtro solo a esas filas.
Además, si siempre está buscando palabras enteras, podría usar una función para simbolizar la cadena en una matriz de palabras, y usar un índice GIN incorporado regular (no pg_trgm) en esa función de retorno de matriz.
fuente
He encontrado una forma de estafa el planificador de consulta, es un truco muy simple:
EXPLAIN
salida:Entonces, como no hay índice
lower(lower(unaccent(label)))
, esto crearía un escaneo secuencial, por lo que se convertirá en un filtro simple. Lo que es más, un simple y también hará lo mismo:Por supuesto, esta es una heurística que puede no funcionar bien, si la parte recortada utilizada en la exploración del índice es muy común. Sin embargo, en nuestra base de datos, no hay realmente mucho la repetición, si utilizo unos 10-15 caracteres.
Quedan dos preguntas pequeñas:
fuente