Como Ler um EXPLAIN do MySQL de Verdade — Guia Completo 2026.
Todo mundo roda EXPLAIN. Poucos leem o que ele realmente está dizendo — é o tricorder mais subutilizado do banco de dados.
Diário de bordo, DBA sênior, estardalhaço nº 58.
Quase todo desenvolvedor já rodou EXPLAIN na frente de uma query lenta. Poucos sabem realmente o que a saída está dizendo. É como entregar um tricorder para alguém que nunca recebeu treinamento: o aparelho aponta exatamente onde está o problema, emite o sinal certo, e mesmo assim a pessoa olha para os números, não reconhece o padrão, encolhe os ombros e sai catando índice novo no escuro — às vezes piorando o que já funcionava.
EXPLAIN não é um veredito de "bom" ou "ruim". É um plano de execução: a estratégia que o otimizador do MySQL decidiu usar para responder sua query, coluna por coluna, decisão por decisão. Ler direito significa entender cada coluna, saber quais valores são sinal verde e quais são red alert, e — principalmente — saber a diferença entre EXPLAIN (o plano estimado) e EXPLAIN ANALYZE (o plano real, medido em execução de verdade). Este guia é o que normalmente ensinamos em Health Checks de MySQL para times que já usam EXPLAIN no dia a dia, mas nunca tiveram alguém para traduzir a saída em decisão prática.
01
EXPLAIN VS. EXPLAIN ANALYZE — MAPA ESTELAR OU SENSOR EM TEMPO REAL
A confusão mais comum começa aqui, então vale separar logo de cara:
EXPLAINmostra o plano que o otimizador pretende seguir, com estimativas baseadas em estatísticas da tabela. Não executa a query. É rápido, seguro de rodar até em produção, mas os números derowsefilteredsão estimativa — podem estar bem longe da realidade se as estatísticas estiverem desatualizadas.EXPLAIN ANALYZE(desde o MySQL 8.0.18) executa a query de verdade e devolve o plano com números reais: tempo gasto em cada etapa, linhas realmente examinadas, quantas vezes cada operação rodou (loops). É a diferença entre olhar a rota no mapa estelar e realmente ligar os sensores e medir o que está lá.
EXPLAIN SELECT p.id, c.nome FROM pedidos p JOIN clientes c ON c.id = p.cliente_id WHERE p.status = 'pendente'; -- Plano real, executa a query e mede tudo EXPLAIN ANALYZE SELECT p.id, c.nome FROM pedidos p JOIN clientes c ON c.id = p.cliente_id WHERE p.status = 'pendente';
Cuidado em produção: EXPLAIN ANALYZE executa SELECT normalmente — sem risco de alterar dado —, mas se a query for pesada, ela vai consumir recurso de verdade, porque está rodando de verdade. Para queries UPDATE/DELETE pesadas, teste em ambiente espelhado antes.
02
A COLUNA TYPE — A ESCALA DE ACESSO, DO PHASER PRECISO AO RED ALERT
type é a coluna mais importante do EXPLAIN tradicional: descreve como o MySQL vai acessar a tabela. Existe uma ordem clara do melhor para o pior:
| type | O que significa | Leitura |
|---|---|---|
| system / const | No máximo uma linha, resolvida por chave primária/única constante | Ideal |
| eq_ref | Uma linha por combinação de JOIN, via chave única | Ótimo |
| ref | Múltiplas linhas via índice não único | Bom |
| range | Varredura de um intervalo do índice (BETWEEN, >, <, IN) | Aceitável |
| index | Varre o índice inteiro, não a tabela — melhor que ALL, mas ainda lê tudo | Atenção |
| ALL | Full table scan — lê a tabela inteira, sem índice | Red alert |
EXPLAIN SELECT id FROM pedidos WHERE cliente_id = 1234; -- type: ref → bom, usando índice em cliente_id EXPLAIN SELECT id FROM pedidos WHERE status = 'pendente'; -- type: ALL → se status não tem índice seletivo, aqui está o problema
Ver type: ALL não é automaticamente um erro — em tabela pequena, ou quando a query realmente precisa da maior parte das linhas, full scan pode ser o plano mais eficiente mesmo com índice disponível. O problema é type: ALL numa tabela grande, numa query que deveria filtrar poucas linhas.
03
POSSIBLE_KEYS VS. KEY — A FROTA DISPONÍVEL CONTRA A NAVE ESCALADA
possible_keys lista todos os índices que poderiam ser usados para essa query. key mostra qual o otimizador escolheu de fato. Quando key vem NULL mesmo com possible_keys preenchido, é sinal de alerta: existe índice candidato, mas o otimizador decidiu que não compensa usá-lo — geralmente porque a seletividade estimada é baixa demais para justificar o custo do índice frente a um scan direto.
EXPLAIN SELECT id FROM pedidos WHERE status = 'pendente'; -- possible_keys: idx_status -- key: NULL -- Motivo provável: baixa cardinalidade em status (poucos valores distintos), -- o otimizador prefere ler a tabela inteira a saltar entre páginas do índice
Quando isso acontece e você tem certeza de que o índice deveria ser usado, vale checar se as estatísticas da tabela estão desatualizadas (ANALYZE TABLE pedidos;) antes de qualquer mudança estrutural.
04
ROWS E FILTERED — A ESTIMATIVA DA TRIPULAÇÃO DE RECONHECIMENTO
rows é a estimativa de quantas linhas o MySQL espera examinar nessa etapa — não quantas vai devolver. filtered (em porcentagem) estima quantas dessas linhas sobrevivem aos filtros adicionais do WHERE que não fazem parte do acesso via índice. Multiplicar rows × filtered dá a estimativa real de linhas relevantes.
-- rows: 500000, filtered: 2.00 -- Leitura: o otimizador espera examinar 500 mil linhas e reter só 2% delas (10 mil) -- Se a query devolve poucas dezenas de linhas na prática, a estimativa está longe — -- e provavelmente falta um índice melhor cobrindo o filtro real
Esses dois números são estimativa baseada em estatísticas — podem estar desatualizados. Quando a distância entre rows estimado e o volume real de dados devolvido é grande, é sinal para rodar EXPLAIN ANALYZE e ver o número real, não confiar cegamente na estimativa.
05
A COLUNA EXTRA — OS AVISOS QUE O TRICORDER SUSSURRA
Extra é onde o EXPLAIN conta o que mais importa e a maioria ignora. Os sinais mais relevantes:
Using index— ótimo sinal: a query foi respondida só com dados do índice, sem tocar a tabela (índice de cobertura).Using where— normal, indica filtro adicional aplicado depois do acesso ao índice/tabela. Sozinho não é problema.Using temporary— o MySQL precisou criar uma tabela temporária para resolverGROUP BY,DISTINCTou certosORDER BY. Em volume alto, é custo real de I/O e memória.Using filesort— o resultado precisou ser ordenado fora do índice, geralmente em memória ou, se não couber, em disco. Sinal claro de que falta um índice alinhado aoORDER BY.Using join buffer— o JOIN não conseguiu usar índice na tabela do lado direito e precisou montar um buffer em memória para comparar linha a linha. Normalmente indica índice faltando na coluna de junção.Range checked for each record— o pior sinal combinado: o MySQL reavalia qual índice usar para cada linha externa do JOIN, porque nenhuma estratégia fixa funciona bem. Quase sempre aponta para schema ou query mal desenhados.
EXPLAIN SELECT nome FROM clientes ORDER BY nome LIMIT 10; -- Extra: Using filesort -- Sem índice em nome, o MySQL ordena o resultado "na mão" antes de aplicar o LIMIT
Using temporary e Using filesort juntos, na mesma query, é o combo clássico de GROUP BY ou ORDER BY sem suporte de índice — normalmente o primeiro lugar para procurar quando uma query de relatório está lenta.
DIAGNÓSTICO COMPLETO
🔍 MySQL Health Check.
Auditoria completa de queries, planos de execução, índices e concorrência — entregue em até 5 dias úteis com plano de ação priorizado por consumo real de recurso.
Solicitar Health Check →06
LENDO EXPLAIN ANALYZE — A ÁRVORE DE EXECUÇÃO REAL
EXPLAIN ANALYZE devolve uma árvore hierárquica em texto, de dentro para fora: as operações mais internas (acesso a tabela, uso de índice) aparecem indentadas dentro das operações que as consomem (JOIN, ORDER BY, LIMIT). Cada nó traz:
actual time— tempo real em milissegundos até a primeira linha e até a última linha daquela etapa.rows— número real de linhas produzidas por essa etapa (não estimativa).loops— quantas vezes essa etapa foi executada (relevante em JOINs aninhados).
EXPLAIN ANALYZE SELECT p.id, c.nome FROM pedidos p JOIN clientes c ON c.id = p.cliente_id WHERE p.status = 'pendente' ORDER BY p.criado_em DESC LIMIT 20;
-> Limit: 20 row(s) (actual time=45.2..45.4 rows=20 loops=1)-> Sort: p.criado_em DESC (actual time=45.1..45.3 rows=20 loops=1)-> Nested loop inner join (actual time=0.8..44.6 rows=312 loops=1)-> Filter: (p.status = 'pendente') (actual time=0.5..12.3 rows=312 loops=1)-> Index range scan on pedidos using idx_status (actual time=0.4..11.8 rows=312 loops=1)-> Single-row index lookup on c using PRIMARY (actual time=0.09..0.09 rows=1 loops=312)
A leitura de cima para baixo: o Sort (equivalente ao Using filesort do EXPLAIN clássico) está consumindo praticamente todo o tempo até o Limit, comparado ao Nested loop join que é rápido. Isso diz exatamente onde investir: um índice composto (status, criado_em) eliminaria o Sort explícito, porque as linhas já sairiam do índice na ordem certa.
07
O ERRO MAIS COMUM: OTIMIZAR PELA ESTIMATIVA, NÃO PELO REAL
O padrão mais frequente que vemos em times que já usam EXPLAIN: eles leem rows do EXPLAIN tradicional, concluem que a query é cara, criam um índice novo — e a query continua lenta, porque o gargalo real nunca esteve no acesso à tabela, e sim num Sort ou numa tabela temporária que só aparece claramente no EXPLAIN ANALYZE.
A ordem correta de investigação:
- Rode
EXPLAIN ANALYZE, não sóEXPLAIN. - Identifique qual nó da árvore consome a maior fatia de
actual time. - Só então decida se o problema é acesso a índice (
type,key), ordenação (Sort/filesort), agregação (Using temporary) ou junção (join buffer). - Trate a causa raiz identificada — não a primeira coisa que parece suspeita na leitura rápida. Muitos desses gargalos vêm de padrões cobertos em Erros que Todo Desenvolvedor Comete ao Escrever Queries MySQL.
CONSULTORIA MYSQL
Plano de execução mudou e ninguém sabe por quê?
Investigação de planos, estatísticas, índices e comportamento do otimizador — com quem já viu esse mesmo padrão em mais de 1.000 servidores em produção.
Falar com um especialista →08
EXPLAIN VISUAL — QUANDO A ÁRVORE DE TEXTO NÃO BASTA
Para planos com muitos JOINs ou subqueries aninhadas, ler a árvore em texto puro fica difícil de acompanhar. O MySQL Workbench oferece Visual EXPLAIN, que renderiza a mesma árvore graficamente, com o custo relativo de cada nó representado por espessura de linha — útil para apresentar o achado para quem não lê EXPLAIN no dia a dia, como parte de um relatório técnico.
-- No MySQL Workbench: Query → Explain Current Statement (Ctrl+Alt+X) -- gera a árvore visual a partir do mesmo EXPLAIN ANALYZE por trás
Para automação e monitoramento contínuo (não só investigação pontual), sys.statements_with_runtimes_in_95th_percentile combinado com EXPLAIN ANALYZE nas queries do topo do ranking é o fluxo que usamos para priorizar o que revisar primeiro, em vez de analisar tudo manualmente — o mesmo raciocínio que aplicamos em Performance Tuning MySQL 8.4.
CHECKLIST FINAL — LEITURA DE EXPLAIN ANTES DE OTIMIZAR
- Rodar
EXPLAIN ANALYZE, não sóEXPLAIN, sempre que possível em ambiente de teste. - Verificar
type— investigar qualquerALLem tabela grande. - Comparar
possible_keyscomkey— investigarkey: NULLquando havia candidato. - Checar
rowsefilteredestimados contra o volume real devolvido pela query. - Ler
Extralinha por linha —Using filesort,Using temporaryeUsing join buffersão os três sinais mais acionáveis. - Na árvore do
EXPLAIN ANALYZE, identificar o nó com maioractual timeantes de decidir a correção. - Rodar
ANALYZE TABLEquando a estimativa parecer muito distante da realidade. - Nunca criar índice novo baseado só na leitura rápida de
rows— confirmar contra oactual timereal primeiro, e cruzar com o inventário de índices existentes (ver Índices Demais no MySQL).
QUANDO CHAMAR UM DBA ESPECIALISTA
Ler EXPLAIN de uma query isolada é uma habilidade que qualquer desenvolvedor sênior consegue desenvolver com este guia. O que normalmente exige um especialista é o padrão agregado: dezenas de planos de execução mudando de comportamento entre ambientes (dev, homologação, produção) por causa de volume de dado ou estatística desatualizada, ou um otimizador escolhendo planos diferentes para a mesma query em momentos distintos do dia, sob carga variável.
É exatamente esse tipo de investigação que fazemos em todo Health Check de banco de dados da HTI — cruzando EXPLAIN ANALYZE das queries do topo do pt-query-digest com o comportamento real de índices, descrito com mais detalhe em Índices Demais no MySQL, e com os padrões de escrita de query cobertos em Erros que Todo Desenvolvedor Comete ao Escrever Queries MySQL.
PRÓXIMO PASSO
Interpretar EXPLAIN é a diferença entre corrigir causa raiz e criar mais um índice inútil.
E isso raramente está documentado no ORM que sua equipe usa no dia a dia.
PERGUNTAS FREQUENTES
Qual a diferença prática entre EXPLAIN e EXPLAIN ANALYZE?
EXPLAIN mostra o plano estimado, sem executar a query — rápido e seguro em qualquer ambiente. EXPLAIN ANALYZE executa a query de verdade e devolve tempo real, linhas reais e número de repetições de cada etapa. Use EXPLAIN para checagens rápidas e EXPLAIN ANALYZE quando precisar confirmar o comportamento real, de preferência em ambiente de teste para queries pesadas.
type: ALL sempre significa que falta um índice?
Não necessariamente. Em tabelas pequenas, ou em queries que realmente precisam ler a maior parte das linhas, full table scan pode ser o plano mais eficiente mesmo com índice disponível — o otimizador evita usar um índice quando o custo de saltar entre páginas supera o de ler a tabela sequencialmente. O sinal de alerta é type: ALL numa tabela grande com uma query que deveria filtrar poucas linhas.
Por que key aparece NULL mesmo com possible_keys preenchido?
Porque o otimizador avaliou o índice candidato e decidiu que ele não compensa — geralmente por baixa seletividade estimada. Antes de forçar o índice, vale rodar ANALYZE TABLE para atualizar as estatísticas e confirmar se a decisão do otimizador continua a mesma com dados atualizados.
Using temporary e Using filesort sempre são um problema?
Em volumes pequenos, o custo é irrelevante. Em tabelas grandes ou queries executadas com alta frequência, os dois indicam trabalho extra de CPU e possivelmente I/O em disco — o primeiro lugar para investigar é se existe um índice composto que já entregue os dados na ordem ou agrupamento necessário, eliminando a etapa extra.
Preciso rodar EXPLAIN ANALYZE em produção para diagnosticar um problema real?
Idealmente não — o ideal é reproduzir o mesmo volume e padrão de dados em ambiente de teste. Quando isso não é possível e a query é SELECT, EXPLAIN ANALYZE é seguro quanto à integridade dos dados, mas consome recurso real porque executa a query de verdade — rode em horário de menor tráfego e com atenção ao impacto.
Continue lendo
- → Consultoria MySQL — visão completa do serviço
- → Erros que Todo Desenvolvedor Comete ao Escrever Queries MySQL
- → Índices Demais no MySQL — Guia Completo 2026
- → Performance Tuning MySQL 8.4 — Guia Completo 2026
- → Buffer Pool no MySQL: Dimensionamento na Prática — Guia Completo 2026
- → Health Check de banco de dados