01
Por que o LIKE com coringa à esquerda vira varredura da tabela inteira
Um índice B-tree guarda os valores ordenados pelo começo do texto. Ele resolve nome LIKE 'paraf%', porque sabe onde começam as palavras que iniciam com "paraf". Ele não resolve nome LIKE '%inox%', porque "inox" pode estar em qualquer posição, e a única forma de saber é ler cada linha. Com 300 mil produtos, ler tudo cabia na memória e custava pouco. Com 4,2 milhões, a consulta lê 310 mil páginas, boa parte do disco, e expulsa do cache as páginas que as outras telas usavam.
-- A busca atual: coringa dos dois lados, sem distinguir acento nem maiuscula.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, nome, preco
FROM produtos
WHERE ativo
AND (nome ILIKE '%' || 'parafuso inox' || '%' OR descricao ILIKE '%' || 'parafuso inox' || '%')
ORDER BY nome
LIMIT 20;
-- Resultado tipico com 4,2 milhoes de linhas:
-- Limit (actual time=1793.402..1793.411 rows=20)
-- -> Sort (Sort Method: top-N heapsort)
-- -> Seq Scan on produtos (rows=11873)
-- Filter: (ativo AND ((nome ~~* '%parafuso inox%') OR (descricao ~~* '%parafuso inox%')))
-- Rows Removed by Filter: 4188127
-- Buffers: shared hit=48211 read=262904
-- O indice B-tree em nome nao serve: ele ordena pelo inicio do texto, e o coringa
-- a esquerda obriga a ler a tabela inteira. "inox parafuso" e "parafusos" nem encontram nada.O tempo é só metade do problema. O LIKE compara caracteres, não palavras: não entende plural, não ignora acento, exige que os termos apareçam na ordem digitada e não tem noção de relevância. O resultado é ordenado por nome, então o produto que tem "parafuso inox" no título aparece misturado com o que só cita a expressão no meio da descrição. A busca é lenta e, ao mesmo tempo, ruim.
| Padrão de consulta | Usa B-tree comum? | O que acontece com milhões de linhas |
|---|---|---|
| nome = 'Parafuso M8' | Sim | Busca pontual, milissegundos |
| nome LIKE 'Paraf%' | Sim, com collation C ou text_pattern_ops | Varredura de faixa do índice, rápida |
| nome LIKE '%inox%' | Não | Seq Scan: lê a tabela inteira a cada busca |
| nome ILIKE '%inox%' | Não | Seq Scan e ainda converte maiúsculas linha a linha |
| lower(nome) LIKE '%inox%' | Só com índice de expressão, e mesmo assim não | Seq Scan, o coringa à esquerda continua impedindo |