Índices Demais no MySQL — O Guia Completo para Auditar, Quarentenar e Remover em 2026.
Índices são a tripulação da ponte do seu banco. Quando sobra gente demais ali, ninguém navega — só se esbarra.
Diário de bordo, DBA sênior, estardalhaço nº 47.
Toda aplicação começa como uma nave no primeiro dia de comissionamento: enxuta, rápida, cada sistema com uma função clara. Alguém cria um índice para acelerar uma consulta. Funciona. Seis meses depois, todo Pull Request que passa perto de uma tabela lenta ganha um índice novo "só para garantir". Ninguém remove nada — remover parece arriscado, e é mais fácil adicionar. Dois anos depois você tem uma tabela de pedidos com 14 índices, sete deles nunca tocados por um EXPLAIN, e o banco se comporta como uma ponte de comando lotada de tenentes que ninguém escalou para o turno: muita gente, pouco espaço, e a primeira vez que alguém precisa se mexer rápido, tudo trava.
Isso não é folclore de banco de dados. É o padrão mais comum que encontramos em Health Checks de MySQL na HTI: bancos que sofrem não por falta de índice, mas por excesso — e, pior ainda, por índices que existem só para consumir recurso sem nunca responder a uma query. Este guia mostra por que isso acontece, como localizar os culpados no MySQL 8.0/8.4, como testar a remoção sem drama, e — a parte que mais gente pula — os riscos reais de dropar um índice sem entender o que "pouco uso" realmente significa.
01
ÍNDICES NÃO SÃO TRIPULAÇÃO DE RESERVA — CADA UM CUSTA COMBUSTÍVEL
Todo índice em InnoDB é uma estrutura B-tree própria, mantida em paralelo à tabela. Isso significa que ele não é gratuito em nenhum momento da vida do dado:
- Toda escrita paga o preço de todo índice. Um
INSERT,UPDATEouDELETEnão toca só a linha — ele recalcula e rebalanceia cada B-tree de cada índice afetado. Dez índices numa tabela significam, na prática, até dez escritas físicas para uma única escrita lógica. - Cada índice compete por buffer pool. Páginas de índice pouco usadas ainda ocupam espaço em memória, empurrando para fora páginas de dados e índices que importam — o efeito prático é mais cache miss em tudo, inclusive no que é crítico.
- Índices aumentam o redo log e o binlog. Mais estruturas para manter significa mais WAL gerado por transação, mais I/O de log, mais banda consumida em replicação.
- O otimizador também paga o preço. Com índices redundantes ou de baixa seletividade, o otimizador do MySQL gasta mais tempo decidindo qual plano usar — e ocasionalmente escolhe errado, justamente por causa de estatísticas conflitantes entre índices parecidos.
Índice não é tripulação de reserva parada sem custo na baía. É gente na folha de pagamento, consumindo energia da nave o tempo todo, mesmo quando está de folga.
02
O ÍNDICE FANTASMA — NUNCA ESCALADO, MAS SEMPRE A BORDO
Existe um problema irmão do "índice demais", e ele é ainda mais traiçoeiro: o índice que nunca é usado por nenhuma query, mas segue pago em toda escrita, para sempre, silenciosamente. É a versão banco-de-dados do personagem que aparece na escalação, veste o uniforme, mas nunca é chamado para nenhuma missão — só está ali consumindo recursos do replicador.
Ele normalmente nasce de três formas:
- Um índice criado para uma feature que foi descontinuada ou reescrita, e ninguém voltou para limpar.
- Um índice "copiado" de outro ambiente ou de um script de migração antigo, sem validação real de uso.
- Um índice redundante — por exemplo,
(cliente_id)quando já existe(cliente_id, criado_em). O primeiro nunca é escolhido pelo otimizador, porque o segundo já cobre o mesmo caso com mais informação.
O problema não é o índice em si — é que ele parece inofensivo ("está ali, não faz mal") quando na verdade está pagando pedágio em toda escrita, sem nunca devolver o investimento em nenhuma leitura.
03
O IMPACTO REAL — IOPS, CPU, LOCKS E A ZONA NEUTRA DA CONCORRÊNCIA
Isso não é abstrato — cada excesso de índice se traduz em métrica mensurável:
IOPS
Cada B-tree adicional gera páginas extras que precisam ser lidas e escritas em disco quando não cabem em buffer pool. Em volumes altos de escrita, times que auditamos frequentemente descobrem que 30–40% do I/O de disco é manutenção de índices que não sustentam nenhuma query relevante.
CPU
Manter múltiplas B-trees balanceadas consome ciclos de CPU em toda transação. Em picos de carga — Black Friday, fechamento de mês, campanha de marketing — esse overhead de CPU compete diretamente com o processamento que realmente gera receita.
Locks e concorrência
Aqui mora o efeito mais subestimado. Operações de manutenção de índice (rebalanceamento de página, split de página B-tree) podem segurar locks internos do InnoDB por mais tempo do que o esperado, especialmente sob alta concorrência de escrita na mesma tabela. E quando chega a hora de finalmente arrumar a casa, um ALTER TABLE ... DROP INDEX mal planejado em tabela grande pode segurar um metadata lock, travando toda leitura e escrita na tabela até a operação terminar — o equivalente a soar red alert sem necessidade e travar a ponte inteira porque alguém quis reorganizar um armário.
O resultado combinado — mais IOPS, mais CPU, mais contenção de lock — é exatamente o que os times sentem como "a aplicação está lenta" sem conseguir apontar uma causa única. Não é uma query. É a soma de dez índices pagando pedágio o tempo todo, e nenhuma dashboard tradicional de CPU/memória mostra isso de forma óbvia — é preciso olhar dentro do MySQL.
DIAGNÓSTICO COMPLETO
🔍 MySQL Health Check.
Auditoria completa de índices, performance, segurança e alta disponibilidade — entregue em até 5 dias úteis com plano de ação priorizado.
Solicitar Health Check →04
COMO LOCALIZAR OS ÍNDICES PROBLEMÁTICOS (O TRICORDER DO DBA)
MySQL 8.0+ já vem com a instrumentação necessária ligada por padrão — o trabalho é saber onde apontar o tricorder.
Para achar índices redundantes ou duplicados (o clássico (a) que sobrevive à sombra de (a, b)), o pt-duplicate-key-checker do Percona Toolkit continua sendo a ferramenta mais confiável:
O detalhe que decide o resultado da investigação: a janela de tempo. sys.schema_unused_indexes só reflete o que aconteceu desde o último restart do MySQL ou do último TRUNCATE das tabelas do performance_schema. Se seu servidor está de pé há 6 dias e o índice em questão só é usado no fechamento mensal, ele vai aparecer como "não usado" — e não é. Regra de ouro: nunca conclua nada com menos de um ciclo de negócio completo — normalmente 30 a 90 dias, cobrindo fechamento de mês, e se aplicável, um pico sazonal conhecido (Black Friday, Natal, início de trimestre fiscal).
05
COMO ELEGER CANDIDATOS À REMOÇÃO SEM FUZILEIRO DE PLANTÃO
Nem todo índice pouco usado é descartável, e nem todo índice muito usado é indispensável. O critério é uma combinação de sinais, não um único número:
- Zero ou quase zero leituras no período analisado (mínimo 30–90 dias, cobrindo o ciclo de negócio completo).
- Não é redundante com outro índice mais amplo que já cobre o mesmo prefixo de colunas.
- Não sustenta constraint —
UNIQUEque impede duplicidade de dado real, ou índice que dá suporte a umaFOREIGN KEY. Esses dois exigem análise à parte (item 09). - Não é usado só em réplica — índices de leitura usados exclusivamente por relatórios, BI ou jobs batch que rodam contra uma réplica podem não aparecer no monitoramento do primário.
- Tamanho relevante — priorize índices grandes (maior custo de manutenção) sobre índices pequenos em tabelas pequenas, onde o ganho de remoção é irrelevante frente ao risco.
- Baixa cardinalidade — índices sobre colunas com poucos valores distintos (status com 3 estados) raramente ajudam o otimizador e são geralmente seguros de revisar primeiro.
O objetivo dessa etapa não é decidir "remover ou não" — é produzir uma lista priorizada de candidatos, do mais óbvio ao mais arriscado, para tratar na sequência com o nível de cautela correto para cada um.
06
DESABILITAR ANTES DE REMOVER — O MODO DE CAMUFLAGEM
O MySQL 8.0 introduziu um recurso feito sob medida para esse cenário: índices invisíveis. Um índice invisível continua existindo, sendo mantido em toda escrita (então o custo de manutenção não desaparece ainda), mas o otimizador para de considerá-lo em planos de execução. Na prática, é o equivalente a ativar o escudo de camuflagem sem desmontar a nave: se alguém sentir falta, é reversível em milissegundos.
Essa é a etapa que separa uma remoção de índice responsável de uma aposta. Antes de qualquer DROP INDEX definitivo, o índice candidato passa primeiro por invisibilidade — sem custo de rollback, sem operação de DDL pesada, sem lock de metadado prolongado. Referência do padrão que aplicamos em Performance Tuning MySQL 8.4.
07
QUARENTENA — A BAÍA MÉDICA ANTES DA ALTA
Com o índice invisível, começa o período de observação — a quarentena. Esse é o passo que mais gente pula, e é exatamente o que evita incidente em produção:
- Duração mínima recomendada: 2 a 4 semanas, cobrindo pelo menos um ciclo de fechamento e, idealmente, qualquer pico sazonal conhecido do negócio.
- Monitore ativamente, não passivamente: slow query log, APM da aplicação,
EXPLAINde queries suspeitas voltando comtype: ALL(full table scan) onde antes não voltava. - Acompanhe réplicas separadamente — um índice pode estar "invisível" só no nó testado; se a aplicação lê de réplicas com configuração distinta, valide lá também.
- Documente o experimento — qual índice, quando foi tornado invisível, quem aprovou, e qual é o critério de rollback. Isso vira o seu log de bordo — útil tanto para auditoria quanto para o próximo DBA que herdar o ambiente.
Se a quarentena passa inteira sem nenhum sintoma — sem regressão de plano, sem aumento de latência, sem reclamação — o índice está clinicamente liberado para remoção definitiva.
CONSULTORIA MYSQL
Quarentena de índice sem plantonista?
Auditoria, teste em modo invisível, quarentena monitorada e remoção definitiva — feitos com quem já viu esse padrão de incidente em mais de 1.000 servidores em produção.
Falar com um especialista →08
REMOÇÃO DEFINITIVA SEM DETONAR O NÚCLEO DE DOBRA
Passada a quarentena, a remoção em si é o passo tecnicamente mais simples — mas ainda exige cuidado em tabelas grandes:
Em tabelas grandes (dezenas de milhões de linhas ou mais), um DROP INDEX direto pode segurar um metadata lock tempo suficiente para travar toda a tabela em produção. Nesse cenário, use uma ferramenta de DDL online:
Regras práticas que seguimos em produção: executar em janela de baixo tráfego, backup validado nas últimas 24–48h antes da operação, e um responsável de plantão durante a execução, não só um script disparado e esquecido. Remover um índice não é uma operação de risco zero — é uma operação de risco baixo quando precedida de invisibilidade e quarentena, e de risco real quando feita direto, no calor do momento, sem esses dois passos. Em incidente ativo, essa é rotina de atendimento emergencial.
09
OS RISCOS REAIS DE DROPAR SEM ENTENDER O QUE 'POUCO USO' SIGNIFICA
Esta é a seção que mais gente ignora, e é onde moram os incidentes de verdade. "Pouco uso" não é sinônimo de "sem função", e um índice removido às pressas pode gerar consequências muito piores do que o overhead que ele causava:
Constraint disfarçada de índice
Um índice UNIQUE "pouco lido" ainda pode ser a única coisa impedindo duplicidade de CPF, e-mail ou pedido na aplicação. Removê-lo por engano não deixa o banco mais lento — deixa o banco mentiroso, aceitando dados que a regra de negócio nunca deveria permitir. Esse tipo de erro fica ainda mais crítico sob exigências da LGPD.
Suporte a FOREIGN KEY
Índices que sustentam uma FK não podem ser removidos sem antes tratar a constraint — o MySQL normalmente impede isso, mas em cadeias de FK complexas o erro só aparece na hora errada.
Uso sazonal fora da janela observada
Um índice usado apenas no fechamento anual, numa auditoria fiscal ou numa campanha que só acontece uma vez por ano pode parecer "morto" em uma janela de 30 dias e causar uma query de minutos exatamente no pior momento possível — o fechamento contábil de dezembro.
Uso exclusivo por réplica, relatório ou job externo
Ferramentas de BI, exportações agendadas, integrações via ETL ou até um dashboard de terceiro podem depender de um índice que nunca aparece no monitoramento do banco primário. Ambientes com alta disponibilidade e replicação exigem essa checagem cruzada obrigatoriamente.
Dependência oculta em ORM
Frameworks como Hibernate, Sequelize ou Eloquent geram queries dinamicamente; um índice pode ser essencial para um caminho de código raramente executado (ex: exportação sob demanda, relatório gerencial mensal) que não passou pela sua amostra de monitoramento.
Efeito cascata sob carga
Sem o índice, uma query que antes era um seek de milissegundos vira full table scan. Em tabela grande e sob concorrência alta, isso não é "um pouco mais lento" — é o suficiente para saturar CPU, estourar connection pool e derrubar a aplicação inteira, um efeito dominó que lembra mais uma violação de casco do que uma otimização.
Invisível primeiro, nunca DROP direto. Quarentena de ciclo completo, nunca amostra curta. Validação explícita de constraints, FKs e consumidores externos — não depois que o incidente já aconteceu.
10
CHECKLIST FINAL — AUDITORIA DE ÍNDICES MYSQL 2026
sys.schema_unused_indexesesys.schema_index_statisticsrevisados com janela mínima de 30–90 dias.- Redundância de índices verificada com
pt-duplicate-key-checker. - Lista de candidatos classificada por risco: constraint, FK, uso em réplica, sazonalidade.
- Nenhum índice removido diretamente com
DROP INDEXsem passar porINVISIBLEprimeiro. - Quarentena mínima de 2–4 semanas, cobrindo ciclo de fechamento.
- Monitoramento ativo durante a quarentena: slow query log, APM,
EXPLAINde queries críticas. - Remoção em tabela grande feita via
pt-online-schema-changeough-ost, nuncaALTERdireto. - Backup validado nas 24–48h anteriores à remoção definitiva.
- Execução em janela de baixo tráfego, com responsável técnico de plantão.
- Documentação do processo — o que foi removido, quando, por quem, e o critério de rollback.
QUANDO CHAMAR UM DBA ESPECIALISTA
Auditoria de índice parece simples até a segunda ou terceira variável entrar em jogo: réplica com padrão de uso diferente do primário, tabela de centenas de milhões de linhas onde qualquer DDL é um evento planejado, ou um sistema legado sem ninguém que lembre por que aquele índice existe. É exatamente o tipo de decisão em que ter alguém que já viu o mesmo padrão de incidente antes faz a diferença entre uma manutenção tranquila e um chamado de emergência.
A HTI opera mais de 1.000 servidores MySQL em produção, mantém engenheiros que contribuem com o código-fonte do MySQL desde 2004, e já resolveu mais de 596 incidentes críticos — incluindo travamentos causados justamente por DDL de índice mal planejado. Isso está no escopo padrão de qualquer Health Check de banco de dados, de qualquer engajamento de consultoria MySQL, e da operação contínua de DBA Remoto 24×7.
PRÓXIMO PASSO
Auditar e remover índices com segurança.
Exige metodologia, monitoramento e alguém que já viu esse mesmo padrão de falha antes — não é tarefa para tentativa e erro em produção.
PERGUNTAS FREQUENTES
Índice invisível no MySQL realmente não afeta a query?
Correto — um índice INVISIBLE continua sendo mantido em toda escrita (o custo de manutenção não desaparece ainda), mas o otimizador para de considerá-lo em planos de execução. É a forma mais segura de testar o impacto da remoção antes de fazer o DROP definitivo, porque reverter é instantâneo.
Quanto tempo devo manter um índice em quarentena antes de remover de vez?
No mínimo 2 a 4 semanas, cobrindo um ciclo de fechamento completo do negócio. Para sistemas com sazonalidade forte (fiscal, varejo, saúde), vale estender até cobrir o pico conhecido mais próximo antes da remoção definitiva.
sys.schema_unused_indexes é suficiente para decidir o que remover?
É o ponto de partida, não a decisão final. Ele reflete apenas o que foi observado desde o último restart do MySQL, não valida constraints (UNIQUE, FOREIGN KEY) nem uso exclusivo em réplicas, relatórios ou jobs externos — todos precisam ser checados separadamente.
Remover índice trava a aplicação em produção?
Pode, se feito via ALTER TABLE ... DROP INDEX direto em tabela grande, por causa do metadata lock. Em tabelas grandes, a prática recomendada é usar pt-online-schema-change ou gh-ost, que evitam lock prolongado na tabela.
Índice com baixa leitura sempre pode ser removido?
Não necessariamente. Baixa leitura não significa ausência de função — pode ser um índice UNIQUE garantindo integridade de dado, suporte a FOREIGN KEY, ou uso sazonal/exclusivo em réplica que não aparece na janela de monitoramento padrão.
Continue lendo
- → Consultoria MySQL — visão completa do serviço
- → Performance Tuning MySQL 8.4 — Guia Completo 2026
- → Erros em Queries MySQL — Guia de Performance 2026
- → Como Ler um EXPLAIN do MySQL de Verdade — Guia Completo 2026
- → Buffer Pool no MySQL: Dimensionamento na Prática — Guia Completo 2026
- → Consultoria MySQL Alta Disponibilidade
- → Segurança MySQL e LGPD — Checklist Completo 2026
- → DBA Remoto MySQL 24×7 — Quando vale a pena terceirizar?
- → Health Check de banco de dados