Variaciones de rendimiento de consultas de PostgreSQL LIKE

112

He estado viendo una variación bastante grande en los tiempos de respuesta con respecto a las LIKEconsultas a una tabla en particular en mi base de datos. A veces obtendré resultados en 200-400 ms (muy aceptable), pero otras veces puede tomar hasta 30 segundos para devolver los resultados.

Entiendo que las LIKEconsultas consumen muchos recursos, pero no entiendo por qué habría una diferencia tan grande en los tiempos de respuesta. He creado un índice btree en el owner1campo, pero no creo que ayude con las LIKEconsultas. ¿Alguien tiene alguna idea?

SQL de muestra:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

También probé:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

Y:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

Con resultados similares.
Recuento de filas de la tabla: aproximadamente 95.000.

Jason
fuente

Respuestas:

282

FTS no admite LIKE

La respuesta previamente aceptada era incorrecta. La búsqueda de texto completo con sus índices de texto completo no es para el LIKEoperador en absoluto, tiene sus propios operadores y no funciona para cadenas arbitrarias. Funciona con palabras basadas en diccionarios y derivaciones. Se hace de soporte emparejamiento de prefijo para las palabras , pero no con el LIKEoperador:

Índices de trigram para LIKE

Instalar el módulo adicional pg_trgmque proporciona clases de operador de ginebra y de trigramas GiST índices para apoyar todas LIKEy ILIKEpatrones , no sólo los de la izquierda anclados:

Índice de ejemplo:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

O:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

Consulta de ejemplo:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

Trigramas? ¿Qué pasa con las cuerdas más cortas?

Las palabras con menos de 3 letras en valores indexados aún funcionan. El manual:

Se considera que cada palabra tiene dos espacios como prefijo y un espacio como sufijo al determinar el conjunto de trigramas contenidos en la cadena.

¿Y patrones de búsqueda con menos de 3 letras? El manual:

Tanto LIKEpara búsquedas como para búsquedas de expresiones regulares, tenga en cuenta que un patrón sin trigramas extraíbles degenerará en un escaneo de índice completo.

Es decir, que los escaneos de índice / mapa de bits aún funcionan (los planes de consulta para la declaración preparada no se romperán), simplemente no le dará un mejor rendimiento. Por lo general, no hay una gran pérdida, ya que las cadenas de 1 o 2 letras apenas son selectivas (más de un pequeño porcentaje de las coincidencias de la tabla subyacente) y el soporte de índices no mejoraría el rendimiento para empezar, porque un escaneo completo de la tabla es más rápido.


text_pattern_ops para la coincidencia de prefijo

Solo para patrones anclados a la izquierda (sin comodines iniciales), obtiene el óptimo con una clase de operador adecuada para un índice btree: text_pattern_opsovarchar_pattern_ops . Ambas características integradas de Postgres estándar, no se necesitan módulos adicionales. Rendimiento similar, pero índice mucho menor.

Índice de ejemplo:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Consulta de ejemplo:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

O , si debe ejecutar su base de datos con la configuración regional 'C' (de hecho, no configuración regional), entonces todo se ordena según el orden de bytes de todos modos y un índice btree simple con la clase de operador predeterminada hace el trabajo.

Más detalles, explicación, ejemplos y enlaces en estas respuestas relacionadas en dba.SE:

Erwin Brandstetter
fuente
Sin comodines iniciales en una tabla de 500K líneas, el índice de gin con gin_trgm_ops parece ser 10 veces más rápido que btree
nicolas
@nicolas: La comparación depende de muchas variables. Longitud de la clave, distribución de datos, longitud del patrón, posible escaneo de solo índice ... Y lo más importante: versión de Postgres. Los índices GIN se han mejorado sustancialmente en las páginas 9.4 y 9.5. La nueva versión de pg_trgm (que se lanzará con la página 9.6) traerá más mejoras.
Erwin Brandstetter
1
Si entendí bien los documentos, pg_trgmnecesita una cadena de consulta de al menos 3 caracteres de longitud, por ejemplo fo%, no presionaría el índice sino que haría un escaneo. Algo a tener en cuenta.
Tuukka Mustonen
1
@TuukkaMustonen: Buen punto. Bueno, los escaneos de índice (mapa de bits) aún funcionan , simplemente no le permitirán obtener un mejor rendimiento. Agregué algunas aclaraciones arriba.
Erwin Brandstetter
7

Posiblemente los más rápidos son patrones anclados con mayúsculas y minúsculas como los que pueden usar índices. es decir, no hay comodines al principio de la cadena de coincidencia, por lo que el ejecutor puede usar un escaneo de rango de índice. ( el comentario relevante en los documentos está aquí ) Inferior e ilike también perderán su capacidad para usar el índice a menos que cree específicamente un índice para ese propósito (vea índices funcionales ).

Si desea buscar una cadena en el medio del campo, debe buscar en los índices de texto completo o de trigramas . El primero de ellos está en el núcleo de Postgres, el otro está disponible en los módulos contrib.

Hormigas Aasma
fuente
No había pensado en crear un índice con el valor en minúsculas del campo. De esa manera, puedo convertir el texto de la consulta a minúsculas en el backend antes de realizar la consulta.
Jason
4

Puede instalar Wildspeed , un tipo diferente de índice en PostgreSQL. Wildspeed funciona con comodines% word%, no hay problema. La desventaja es el tamaño del índice, que puede ser grande, muy grande.

Frank Heikens
fuente
3

Ejecute la consulta mencionada a continuación para mejorar el rendimiento de la consulta LIKE en postgresql. crea un índice como este para tablas más grandes:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)
Noyal
fuente
Esto solo funciona si el patrón no comienza con un comodín; en este caso, las dos primeras consultas de muestra comienzan con un comodín.
cbz
1

Recientemente tuve un problema similar con una tabla que contiene 200000 registros y necesito hacer consultas LIKE repetidas. En mi caso, se corrigió la cadena que se buscaba. Otros campos variaron. Por eso, pude reescribir:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

como

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

Me encantó cuando las consultas volvieron rápidamente y verifiqué que el índice se estaba utilizando con EXPLAIN ANALYZE:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms
Stephen Quan
fuente
0

Sus consultas similares probablemente no pueden usar los índices que creó porque:

1) su criterio LIKE comienza con un comodín.

2) ha utilizado una función con sus criterios LIKE.

Asaf
fuente
0

Siempre que utilice una cláusula en una columna con funciones, por ejemplo, LIKE, ILIKE, superior, inferior, etc. Entonces, Postgres no tendrá en cuenta su índice normal. Hará un escaneo completo de la tabla pasando por cada fila y por lo tanto será lento.

La forma correcta sería crear un nuevo índice de acuerdo con su consulta. Por ejemplo, si quiero hacer coincidir una columna sin distinción entre mayúsculas y minúsculas y mi columna es un varchar. Entonces puedes hacerlo así.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

Del mismo modo, si su columna es un texto, haga algo como esto

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

Del mismo modo, puede cambiar la función superior a cualquier otra función que desee.

omer Farooq
fuente