Eu tenho uma mesa grande entities
com ~ 15 milhões de registros. Eu quero encontrar as 5 principais linhas correspondentes a 'hockey' na sua name
.
Eu tenho um índice de texto completo name
, usado:gin_ix_entity_full_text_search_name
Inquerir:
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
Duração 25.623 ms
Explique o plano 1 Limite (custo = 12666,89..12666,89 linhas = 5 largura = 3116) 2 -> Classificação (custo = 12666,89..12670,18 linhas = 6571 largura = 3116) 3 Chave de Classificação: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' hockey '' '' :: tsquery))) 4 -> Verificação de heap de bitmap em entidades (custo = 124.06..12645.06 linhas = 6571 width = 3116) 5 Verifique novamente Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery) 6 Filtro: (NÃO coloque) 7 -> Varredura de índice de bitmap no gin_ix_entity_full_text_search_name (custo = 0.00..123.74 linhas = 6625 width = 0) 8 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
Não entendo por que ele verifica a condição do índice duas vezes. (Etapa 4 e 7 do plano de consulta). É por causa da minha condição booleana ( not place
)? Em caso afirmativo, devo adicioná-lo ao meu índice para obter uma consulta muito rápida? Ou a condição de classificação torna lenta?
EXPLAIN ANALYZE
resultado:
Limite (custo = 4447,28..4447,29 linhas = 5 largura = 3116) (tempo real = 18509,274..18509,282 linhas = 5 loops = 1) -> Classificação (custo = 4447,28..4448,41 linhas = 2248 largura = 3116) (tempo real = 18509,271..18509,273 linhas = 5 loops = 1) Chave de classificação: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' test '' ':: :: tsquery))) Método de classificação: top-N heapsort Memória: 19kB -> Varredura de Heap de Bitmap em entidades (custo = 43.31..4439.82 linhas = 2248 largura = 3116) (tempo real = 119.003..18491.408 linhas = 2533 loops = 1) Verifique novamente Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Filtro: (NÃO colocar) -> Varredura de índice de bitmap no gin_ix_entity_full_text_search_name (custo = 0.00..43.20 linhas = 2266 width = 0) (tempo real = 74.093..74.093 linhas = 2593 loops = 1) Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Tempo de execução total: 18509,381 ms
Aqui estão meus parâmetros de banco de dados. Está hospedado em Heroku, nos serviços da Amazon. Eles o descrevem como tendo 1,7 GB de RAM, 1 unidade de processamento e um banco de dados de no máximo 1 TB.
nome | Configuração atual ------------------------------ + ------------------- -------------------------------------------------- ------------------------------------ versão | PostgreSQL 9.0.7 no i486-pc-linux-gnu, compilado pelo GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits arquivo_command | teste -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 | em archive_timeout | 1 minuto checkpoint_completion_target | 0,7 checkpoint_segments | 40. client_min_messages | aviso prévio cpu_index_tuple_cost | 0,001 cpu_operator_cost | 0,0005 cpu_tuple_cost | 0,003 effective_cache_size | 1530000kB hot_standby | em lc_collate | en_US.UTF-8 lc_ctype | en_US.UTF-8 listen_addresses | * log_checkpoints | em log_destination | syslog log_line_prefix | % u [AMARELO] log_min_duration_statement | 50ms log_min_messages | aviso prévio logging_collector | em maintenance_work_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 porto | random_page_cost | 2 server_encoding | UTF8 shared_buffers | 415MB ssl em syslog_ident | resource29857_heroku_com Fuso horário | UTC wal_buffers | 8MB wal_keep_segments | 127 wal_level | Hot Standby work_mem | 100MB (39 linhas)
EDITAR
Parece que ORDER BY
é a parte mais 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. com 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
Ainda não entendi por que isso é mais lento. Parece que está buscando a mesma quantidade de linhas do Bitmap Heap Scan, mas leva muito mais tempo?
Respostas:
Que classificar as linhas custará algo é óbvio. Mas por que tanto?
Sem o
ORDER BY rank0...
Postgres pode apenasts_rank()
apenas 5 linhas.ts_rank()
para todos eles.ORDER BY name
apenas ver o custo da computaçãoto_tsquery('english', 'hockey'::text))
para as linhas supérfluas e quanto resta para buscar mais linhas e classificar.fonte
Uma verificação de bitmap funciona assim: O índice é verificado para encontrar os locais das tuplas que correspondem às condições do índice. O bitmap pode passar por uma combinação lógica com os resultados de outras varreduras de bitmap, usando lógica booleana nos bits. Em seguida, as páginas contendo os dados são acessadas em ordem de número de página, para reduzir o acesso ao disco e, esperamos, transformar algumas leituras aleatórias em leituras seqüenciais.
Uma verificação de índice de bitmap pode precisar se tornar "com perda" para caber na memória - reduz sua precisão do nível da tupla para o nível da página. Se você aumentar work_mem (pelo menos para essa consulta), poderá evitar isso. Não será possível ignorar a verificação de heap de bitmap na linha 4 ou o filtro na linha 6, mas poderá ignorar a verificação novamente na linha 5.
fonte
Como a pergunta editada faz parecer que o objetivo é escolher algumas das principais correspondências, em vez de uma lista de tudo que corresponda, estou postando uma resposta separada para isso, pois é um problema bastante diferente.
Convém considerar um índice KNN - GiST (para K vizinho mais próximo - Árvore de pesquisa generalizada), que pode ser extraído diretamente do índice em ordem de similaridade - para que você não precise ler aleatoriamente todas as tuplas de heap e classificar para baixo para encontrar as melhores combinações de K.
Até a presente data, acho que ninguém implementou o suporte KNN-GIST para consultas tsearch (embora tenha sido garantido que isso pode ser feito, é apenas uma questão de alguém ter tempo para fazê-lo), mas talvez o suporte ao trigrama (que é feito) irá trabalhar para a sua aplicação. A principal diferença é que as pesquisas de trigramas não usam dicionários para derivar e sinônimos da mesma forma que o tsearch; você encontrará apenas correspondências exatas das palavras.
Para experimentar trigramas para o seu exemplo, você provavelmente deseja indexar "nome" assim:
Então você pode pesquisar assim:
Observe os operadores utilizados e o
ORDER BY
uso do alias da coluna "similaridade". Eu não me afastaria muito desse padrão ao testá-lo. O índice no tsvector não é usado para esta pesquisa.Exceto problemas com sua configuração atual (que poderia facilmente lançar toda a sua VM na paginação sem esperança da superalocação de memória), você provavelmente gostará muito do desempenho disso. Se ele tem o comportamento que você quer, é o que eu não sei.
fonte