Tengo una gran mesa entities
con ~ 15 millones de registros. Quiero encontrar las 5 filas superiores que coinciden con 'hockey' en su name
.
Tengo un índice de texto completo name
, que se utiliza:gin_ix_entity_full_text_search_name
Consulta:
SELECT "entities".*,
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE "entities"."place" = 'f'
AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text))
ORDER BY "rank0.48661998202865475" DESC LIMIT 5
Duración 25,623 ms
Explicar el plan 1 Límite (costo = 12666.89..12666.89 filas = 5 ancho = 3116) 2 -> Ordenar (costo = 12666.89..12670.18 filas = 6571 ancho = 3116) 3 Clave de clasificación: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery)) 4 -> Escaneo de montón de mapa de bits en entidades (costo = 124.06..12645.06 filas = 6571 ancho = 3116) 5 Vuelva a verificar Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery) 6 Filtro: (NO lugar) 7 -> Escaneo de índice de mapa de bits en gin_ix_entity_full_text_search_name (costo = 0.00..123.74 filas = 6625 ancho = 0) 8 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
No entiendo por qué verifica la condición del índice dos veces. (Plan de consulta paso 4 y 7). ¿Es por mi condición booleana ( not place
)? Si es así, ¿debo agregarlo a mi índice para obtener una consulta muy rápida? ¿O la condición de clasificación lo hace lento?
EXPLAIN ANALYZE
salida:
Límite (costo = 4447.28..4447.29 filas = 5 ancho = 3116) (tiempo real = 18509.274..18509.282 filas = 5 bucles = 1) -> Ordenar (costo = 4447.28..4448.41 filas = 2248 ancho = 3116) (tiempo real = 18509.271..18509.273 filas = 5 bucles = 1) Clave de clasificación: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery)) Método de clasificación: top-N heapsort Memoria: 19kB -> Escaneo de montón de mapa de bits en entidades (costo = 43.31..4439.82 filas = 2248 ancho = 3116) (tiempo real = 119.003..18491.408 filas = 2533 bucles = 1) Vuelva a verificar Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Filtro: (NO lugar) -> Escaneo de índice de mapa de bits en gin_ix_entity_full_text_search_name (costo = 0.00..43.20 filas = 2266 ancho = 0) (tiempo real = 74.093..74.093 filas = 2593 bucles = 1) Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Tiempo de ejecución total: 18509.381 ms
Aquí están mis parámetros de base de datos. Está alojado por Heroku, en los servicios de Amazon. Lo describen como tener 1,7 GB de RAM, 1 unidad de procesamiento y una base de datos de máximo 1 TB.
nombre | configuración actual ------------------------------ + ------------------- -------------------------------------------------- ------------------------------------ version | PostgreSQL 9.0.7 en i486-pc-linux-gnu, compilado por GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits comando_archivo | prueba -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push% p archive_mode | en archive_timeout | 1 minuto checkpoint_completion_target | 0.7 checkpoint_segments | 40 client_min_messages | aviso cpu_index_tuple_cost | 0.001 cpu_operator_cost | 0,0005 cpu_tuple_cost | 0.003 eficaz_caché_tamaño | 1530000kB hot_standby | en lc_collate | en_US.UTF-8 lc_ctype | en_US.UTF-8 listen_addresses | * * log_checkpoints | en log_destination | syslog log_line_prefix | % u [AMARILLO] log_min_duration_statement | 50ms log_min_messages | aviso logging_collector | en mantenimiento_trabajo_mem | 64MB max_ connections | 500 max_prepared_transactions | 500 max_stack_depth | 2MB max_standby_archive_delay | -1 max_standby_streaming_delay | -1 max_wal_senders | 10 puerto | random_page_cost | 2 codificación_servidor | UTF8 shared_buffers | 415MB ssl | en syslog_ident | resource29857_heroku_com TimeZone | UTC wal_buffers | 8MB wal_keep_segments | 127 wal_level | hot_standby work_mem | 100MB (39 filas)
EDITAR
Parece que ORDER BY
es la parte lenta:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
LIMIT 5;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 103.680 ms
Vs. con ORDER BY
:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
ORDER BY "rank0.48661998202865475" DESC
LIMIT 5;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
-> Sort (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
Sort Method: top-N heapsort Memory: 17kB
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 15013.805 ms
Bit Todavía no entiendo por qué esto es más lento. Parece que está obteniendo la misma cantidad de filas de Bitmap Heap Scan, pero ¿lleva mucho más tiempo?
Respuestas:
Que ordenar las filas costará algo es obvio. ¿Pero por qué tanto?
Sin
ORDER BY rank0...
Postgres solo podemosts_rank()
solo para 5 filas.ts_rank()
para todos ellos.ORDER BY name
solo ver el costo de la computaciónto_tsquery('english', 'hockey'::text))
para las filas superfluas y cuánto queda por recuperar más filas y ordenarlas.fuente
Un escaneo de mapa de bits funciona así: el índice se escanea para encontrar las ubicaciones de las tuplas que coinciden con las condiciones del índice. El mapa de bits podría pasar por una combinación lógica con los resultados de otros escaneos de mapas de bits, utilizando lógica booleana en los bits. Luego, se accede a las páginas que contienen los datos en orden de número de página, para reducir el acceso al disco y, con suerte, convertir algunas lecturas aleatorias en lecturas secuenciales.
Es posible que una exploración de índice de mapa de bits se vuelva "con pérdida" para caber en la memoria; esto reduce su precisión desde el nivel de tupla hasta el nivel de página. Si aumenta work_mem (al menos para esta consulta), podría evitarlo. No podrá omitir la exploración de montón de mapa de bits en la línea 4 o el filtro en la línea 6, pero podría omitir la verificación en la línea 5.
fuente
Dado que la pregunta editada hace que parezca que el objetivo es elegir algunas de las mejores coincidencias, en lugar de una lista de todo lo que coincide, estoy publicando una respuesta separada para eso, ya que es un problema bastante diferente.
Es posible que desee considerar un índice KNN - GiST (para K Vecino más cercano - Árbol de búsqueda generalizada), que puede extraer directamente del índice en orden de similitud, por lo que no necesita leer al azar todas esas tuplas de montón y ordenar hacia abajo para encontrar las K mejores coincidencias.
Hasta la fecha, no creo que nadie haya implementado el soporte KNN-GIST para las consultas tsearch (aunque estoy seguro de que se puede hacer, es solo cuestión de que alguien se tome el tiempo para hacerlo), pero tal vez el soporte trigram (que está hecho) funcionará para su aplicación. La principal diferencia es que las búsquedas de trigram no usan diccionarios para derivaciones y sinónimos como lo hace tsearch; solo encontrarás coincidencias de palabras exactas.
Para probar los trigramas para su ejemplo, probablemente desee indexar "nombre" de esta manera:
Entonces puedes buscar así:
Tenga en cuenta los operadores utilizados y el
ORDER BY
uso del alias de la columna "similitud". No me alejaría demasiado de este patrón al probarlo. El índice en el tsvector no se utiliza para esta búsqueda.A menos que haya problemas con su configuración actual (que fácilmente podría llevar a toda su máquina virtual a una paginación desesperada por sobrecompromiso de memoria), probablemente le gustará realmente el rendimiento de esto. Si tiene el comportamiento que desea es lo que no sé.
fuente