Full-Text Search no MySQL
EXPLAIN antes e depois do FULLTEXT
Comparar planos LIKE vs FULLTEXT: rows, type, Extra e custo relativo.
Nesta aula você vai
- Comparar planos EXPLAIN de LIKE e de MATCH no mesmo corpus
- Ler type, rows, Extra e key em buscas textuais MySQL
- Usar a comparação como argumento objetivo para migrar a funcionalidade de busca
EXPLAIN antes e depois do FULLTEXT
Objetivos
Nesta aula você vai:
- Rodar e interpretar
EXPLAIN(eEXPLAIN ANALYZEquando disponível) em ambos os padrões - Contrastar
type,rows,keyeExtra - Documentar a melhoria com números, não com impressões
Introdução
A migração de busca só se sustenta se o plano melhorar. Nesta aula comparamos, no mesmo schema do OficinaHub, a consulta legada com LIKE e a consulta com MATCH sobre índice FULLTEXT. Os números abaixo são ilustrativos da forma do plano; no seu ambiente, capture os reais.
Conteúdo
Cenário comum de dados
-- Volume de estudo
SELECT COUNT(*) AS total_produtos FROM produtos;
-- ex.: 900000
Consulta de negócio: usuário busca furadeira impacto bateria.
Antes: LIKE
EXPLAIN
SELECT id, nome
FROM produtos
WHERE nome LIKE '%furadeira impacto%'
OR descricao LIKE '%furadeira impacto%'
ORDER BY id DESC
LIMIT 20;
Plano ilustrativo:
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | produtos | ALL | NULL | NULL | 900000 | Using where; Using filesort |
Leitura:
type = ALL: table scankey = NULL: nenhum índice útilrows ≈ N: otimizador espera olhar quase tudoUsing filesort: ordenação extra (aqui porid, não por relevância)
-- Em MySQL 8.0.18+: tempo real
EXPLAIN ANALYZE
SELECT id, nome
FROM produtos
WHERE nome LIKE '%furadeira%'
OR descricao LIKE '%furadeira%'
LIMIT 20;
Depois: FULLTEXT
EXPLAIN
SELECT id, nome,
MATCH(nome, descricao, tags_texto)
AGAINST ('furadeira impacto bateria' IN NATURAL LANGUAGE MODE) AS score
FROM produtos
WHERE MATCH(nome, descricao, tags_texto)
AGAINST ('furadeira impacto bateria' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;
Plano ilustrativo:
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | produtos | fulltext | ft_produtos_busca | ft_produtos_busca | 120 | Using where |
Leitura:
type = fulltext: acesso via índice invertidokey = ft_produtos_busca: índice esperadorowsmuito menor: candidatos, não a tabela inteira- Ordenação por score alinha plano e UX
Tabela comparativa para o PR
| Métrica | LIKE | FULLTEXT |
|---|---|---|
| type | ALL | fulltext |
| rows estimadas | ~N | << N |
| key | NULL | ft_* |
| Relevância | ausente / improvisada | score nativo |
| Comportamento sob 40 sessões | I/O explode | escala com postings |
Armadilhas na comparação
LIMITmascara LIKE: o servidor pode parar cedo em alguns casos, mas sob concorrência e filtros complexos o scan volta a doer- Cache quente: rode várias vezes e descarte a primeira medição
- Consultas diferentes: compare a mesma intenção de busca, não SQL aleatório
- Estatísticas:
ANALYZE TABLE produtos;antes de debates longos de plano
ANALYZE TABLE produtos;
Problema comum e solução
Problema: o EXPLAIN do MATCH ainda mostra custo alto porque as colunas no MATCH não batem com o índice, ou porque há OR com predicados não-fulltext que forçam combinação ruim.
Solução: isole o MATCH puro no plano; só depois reintroduza filtros. Garanta correspondência exata das colunas do índice. Evite OR que misture MATCH com LIKE na mesma disjunção — isso frequentemente degenera o plano.
-- Evite este anti-padrão
SELECT id FROM produtos
WHERE MATCH(nome, descricao, tags_texto) AGAINST ('furadeira')
OR nome LIKE '%furadeira%'; -- pode estragar o acesso fulltext
Como analisar
Roteiro de laboratório:
- Capture
EXPLAIN+ tempo deLIKE - Capture
EXPLAIN+ tempo deMATCH - Repita com carga (ex.: 20 clientes) em staging
- Anexe os dois planos no ticket/PR
EXPLAIN FORMAT=JSON
SELECT id FROM produtos
WHERE MATCH(nome, descricao, tags_texto)
AGAINST ('furadeira impacto bateria' IN NATURAL LANGUAGE MODE)
LIMIT 20;
O JSON facilita diff em revisão de código.
Resumo
LIKE '%...%'→type ALL, rows ~ tabelaMATCHcom índice →type fulltext, rows candidatas- Compare planos no mesmo corpus e documente métricas
- Não misture
OR LIKEao lado doMATCHse quiser preservar o acesso fulltext