Variações de desempenho de consulta do PostgreSQL LIKE

112

Tenho visto uma grande variação nos tempos de resposta em relação LIKE consultas a uma tabela específica em meu banco de dados. Às vezes, obtenho resultados dentro de 200-400 ms (muito aceitável), mas outras vezes pode demorar até 30 segundos para retornar os resultados.

Eu entendo que as LIKEconsultas consomem muitos recursos, mas simplesmente não entendo por que haveria uma diferença tão grande nos tempos de resposta. Eu construí um índice btree no owner1campo, mas não acho que isso ajude com as LIKEconsultas. Alguém tem alguma ideia?

Amostra de SQL:

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

Eu também tentei:

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

E:

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

Com resultados semelhantes.
Contagem de linhas da tabela: cerca de 95.000.

Jason
fonte

Respostas:

280

FTS não suporta LIKE

A resposta aceita anteriormente estava incorreta. A Pesquisa de Texto Completo com seus índices de texto completo não é para o LIKEoperador, ela tem seus próprios operadores e não funciona para strings arbitrárias. Ele opera em palavras com base em dicionários e lematização. Ele faz suporte prefixo correspondente de palavras , mas não com o LIKEoperador:

Índices trigramas para LIKE

Instale o módulo adicional pg_trgmque fornece classes de operador para índices de trigrama GIN e GiST para suportar todos os padrões LIKEeILIKE , não apenas os ancorados à esquerda:

Índice de exemplo:

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

Ou:

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

Consulta de exemplo:

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

Trigramas? E quanto a cordas mais curtas?

Palavras com menos de 3 letras em valores indexados ainda funcionam. O manual:

Cada palavra é considerada como tendo dois espaços prefixados e um espaço sufixado ao determinar o conjunto de trigramas contidos na string.

E os padrões de pesquisa com menos de 3 letras? O manual:

Para pesquisas LIKEe expressões regulares, lembre-se de que um padrão sem trigramas extraíveis irá degenerar para uma varredura de índice completo.

Isso significa que as varreduras de índice / bitmap ainda funcionam (os planos de consulta para a instrução preparada não quebram), apenas não compram melhor desempenho. Normalmente sem grande perda, uma vez que cadeias de 1 ou 2 letras dificilmente são seletivas (mais do que alguns por cento das correspondências da tabela subjacente) e o suporte ao índice não melhoraria o desempenho para começar, porque uma varredura completa da tabela é mais rápida.


text_pattern_ops para correspondência de prefixo

Para padrões ancorados apenas à esquerda (sem curinga inicial), você obtém o ótimo com uma classe de operadores adequada para um índice btree: text_pattern_opsou varchar_pattern_ops. Ambos os recursos integrados do Postgres padrão, nenhum módulo adicional necessário. Desempenho semelhante, mas índice muito menor.

Índice de exemplo:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Consulta de exemplo:

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

Ou , se você deveria estar executando seu banco de dados com a localidade 'C' (efetivamente sem localidade), então tudo é classificado de acordo com a ordem de bytes de qualquer maneira e um índice btree simples com classe de operador padrão faz o trabalho.

Mais detalhes, explicações, exemplos e links nessas respostas relacionadas no dba.SE:

Erwin Brandstetter
fonte
Sem nenhum caractere curinga inicial em uma tabela de 500 mil linhas, o índice gin com gin_trgm_ops parece ser 10 vezes mais rápido que btree
nicolas
@nicolas: A comparação depende de muitas variáveis. Comprimento da chave, distribuição de dados, comprimento do padrão, possível varredura de índice ... E o mais importante: versão Postgres. Os índices GIN melhoraram substancialmente nas páginas 9,4 e 9,5. A nova versão do pg_trgm (a ser lançada na página 9.6) trará mais melhorias.
Erwin Brandstetter
1
Se eu entendi os documentos corretamente, pg_trgmvocê precisa de uma string de consulta de pelo menos 3 caracteres de comprimento, por exemplo fo%, não acertaria o índice, mas faria uma varredura. Algo a ser observado.
Tuukka Mustonen
1
@TuukkaMustonen: Bom ponto. Bem, as varreduras de índice (bitmap) ainda funcionam , mas não lhe proporcionarão um melhor desempenho. Eu adicionei alguns esclarecimentos acima.
Erwin Brandstetter
7

Possivelmente os mais rápidos são padrões ancorados com distinção entre maiúsculas e minúsculas, como os que podem usar índices. ou seja, não há curinga no início da string de correspondência, então o executor pode usar uma varredura de faixa de índice. ( o comentário relevante nos documentos está aqui ) Lower e ilike também perderão sua capacidade de usar o índice, a menos que você crie um índice especificamente para esse propósito (consulte índices funcionais ).

Se você deseja pesquisar uma string no meio do campo, deve examinar os índices de texto completo ou trigrama . O primeiro deles está no núcleo do Postgres, o outro está disponível nos módulos contrib.

Formigas Aasma
fonte
Eu não tinha pensado em criar um índice no valor de minúsculas do campo. Dessa forma, posso converter o texto da consulta em minúsculas no backend antes de fazer a consulta.
Jason
4

Você pode instalar o Wildspeed , um tipo diferente de índice no PostgreSQL. Wildspeed funciona com curingas% word%, sem problemas. A desvantagem é o tamanho do índice, pode ser grande, muito grande.

Frank Heikens
fonte
3

Execute a consulta mencionada abaixo para melhorar o desempenho da consulta LIKE no postgresql. crie um índice como este para tabelas maiores:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)
Noyal
fonte
Isso funciona apenas se o padrão não começar com um curinga - neste caso, as duas primeiras consultas de amostra começam com um curinga.
cbz
1

Recentemente, tive um problema semelhante com uma tabela contendo 200.000 registros e preciso fazer consultas LIKE repetidas. No meu caso, a string que está sendo pesquisada foi corrigida. Outros campos variaram. Por isso, fui capaz de reescrever:

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;

Fiquei encantado quando as consultas voltaram rapidamente e verifiquei se o índice está sendo usado com 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
fonte
0

Suas consultas semelhantes provavelmente não podem usar os índices que você criou porque:

1) seus critérios LIKE começam com um curinga.

2) você usou uma função com seus critérios LIKE.

Asaph
fonte
0

Sempre que você usar uma cláusula em uma coluna com funções, por exemplo, LIKE, ILIKE, superior, inferior, etc. Então o postgres não levará seu índice normal em consideração. Ele fará uma varredura completa da tabela passando por cada linha e, portanto, será lento.

A maneira correta seria criar um novo índice de acordo com sua consulta. Por exemplo, se eu quiser corresponder a uma coluna sem distinção entre maiúsculas e minúsculas e minha coluna é um varchar. Então você pode fazer assim.

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

Da mesma forma, se a sua coluna for um texto, você faz algo assim

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

Da mesma forma, você pode alterar a função superior para qualquer outra função que desejar.

omer Farooq
fonte