Do jeito que você tem, a semelhança entre todos os elementos e todos os outros elementos da tabela deve ser calculada (quase uma junção cruzada). Se sua tabela tiver 1.000 linhas, já são 1.000.000 (!) cálculos de similaridade, antes esses podem ser verificados em relação à condição e classificados. Escala terrivelmente.
Use
SET pg_trgm.similarity_threshold
e o %
operador em vez disso. Ambos são fornecidos pelo pg_trgm
módulo. Desta forma, um índice trigrama GiST pode ser usado com grande efeito. O parâmetro de configuração
pg_trgm.similarity_threshold
substituiu as funções set_limit()
e show_limit()
no Postgres 9.6. As funções obsoletas ainda funcionam (a partir do Postgres 13). Além disso, o desempenho dos índices GIN e GiST melhorou de várias maneiras desde o Postgres 9.1. Tente em vez disso:
SET pg_trgm.similarity_threshold = 0.8; -- Postgres 9.6 or later
SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
FROM names n1
JOIN names n2 ON n1.name <> n2.name
AND n1.name % n2.name
ORDER BY sim DESC;
Mais rápido por ordens de magnitude, mas ainda lento.
pg_trgm.similarity_threshold
é uma opção "personalizada", que pode ser tratada como qualquer outra opção. Ver:- Consulte um parâmetro (configuração postgresql.conf) como "max_connections"
Você pode querer restringir o número de pares possíveis adicionando pré-condições (como combinar as primeiras letras) antes junção cruzada (e suporte isso com um índice funcional correspondente). O desempenho de uma junção cruzada deteriora com O(N²) .
Isso não funciona porque você não pode fazer referência a colunas de saída em
WHERE
ou HAVING
cláusulas:WHERE ... sim > 0.8
Isso está de acordo com o padrão SQL (que é tratado de maneira bastante flexível por alguns outros RDBMS). Por outro lado:
ORDER BY sim DESC
Funciona porque as colunas de saída podem ser usado em
GROUP BY
e ORDER BY
. Ver:- O PostgreSQL reutilizando o resultado da computação na consulta selecionada
Caso de teste
Fiz um teste rápido no meu antigo servidor de teste para verificar minhas declarações.
PostgreSQL 9.1.4. Tempos medidos com
EXPLAIN ANALYZE
(melhor de 5). CREATE TEMP table t AS
SELECT some_col AS name FROM some_table LIMIT 1000; -- real life test strings
Primeira rodada de testes com índice GIN:
CREATE INDEX t_gin ON t USING gin(name gin_trgm_ops); -- round1: with GIN index
Segunda rodada de testes com índice GIST:
DROP INDEX t_gin;
CREATE INDEX t_gist ON t USING gist(name gist_trgm_ops);
Nova consulta:
SELECT set_limit(0.8);
SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
FROM t n1
JOIN t n2 ON n1.name <> n2.name
AND n1.name % n2.name
ORDER BY sim DESC;
Índice GIN usado, 64 hits:tempo de execução total:484,022 ms
Índice GIST usado, 64 hits:tempo de execução total:248,772 ms
Consulta antiga:
SELECT (similarity(n1.name, n2.name)) as sim, n1.name, n2.name
FROM t n1, t n2
WHERE n1.name != n2.name
AND similarity(n1.name, n2.name) > 0.8
ORDER BY sim DESC;
Índice GIN não usado, 64 hits:tempo de execução total:6345,833 ms
índice GIST não usado, 64 hits:tempo de execução total:6335,975 ms
Caso contrário, resultados idênticos. Conselho é bom. E isso é para apenas 1.000 linhas !
GIN ou GiST?
O GIN geralmente oferece desempenho de leitura superior:
- Diferença entre o índice GiST e GIN
Mas não neste caso em particular!
Isso pode ser implementado de forma bastante eficiente por índices GiST, mas não por índices GIN.
- Índice de várias colunas em 3 campos com tipos de dados heterogêneos