Full-Text Search no MySQL

EXPLAIN antes e depois do FULLTEXT

Comparar planos LIKE vs FULLTEXT: rows, type, Extra e custo relativo.

Intermediário 45 min 35 pontos Leitura 0%

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 (e EXPLAIN ANALYZE quando disponível) em ambos os padrões
  • Contrastar type, rows, key e Extra
  • 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 scan
  • key = NULL: nenhum índice útil
  • rows ≈ N: otimizador espera olhar quase tudo
  • Using filesort: ordenação extra (aqui por id, 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 invertido
  • key = ft_produtos_busca: índice esperado
  • rows muito 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

  1. LIMIT mascara LIKE: o servidor pode parar cedo em alguns casos, mas sob concorrência e filtros complexos o scan volta a doer
  2. Cache quente: rode várias vezes e descarte a primeira medição
  3. Consultas diferentes: compare a mesma intenção de busca, não SQL aleatório
  4. 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:

  1. Capture EXPLAIN + tempo de LIKE
  2. Capture EXPLAIN + tempo de MATCH
  3. Repita com carga (ex.: 20 clientes) em staging
  4. 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 ~ tabela
  • MATCH com índice → type fulltext, rows candidatas
  • Compare planos no mesmo corpus e documente métricas
  • Não misture OR LIKE ao lado do MATCH se quiser preservar o acesso fulltext