← Voltar ao hub MariaDB
MariaDB 04 de novembro de 2026 10 min de leitura

MariaDB lento? Como achar e corrigir as consultas que estão derrubando seu sistema

MariaDB lento? Como achar e corrigir as consultas que estão derrubando seu sistema

Antes de pedir um servidor maior, descubra qual consulta está pesando. O MariaDB já traz tudo o que você precisa para isso.

Resumo para quem tem pressa: na grande maioria dos casos de "o banco está lento", o culpado não é o hardware nem o MariaDB, mas um punhado de consultas mal indexadas. O caminho é sempre o mesmo: ligar o slow query log, ranquear as consultas que mais custam, entender o plano com ANALYZE (que mostra o que aconteceu de verdade, não só a estimativa), e corrigir com o índice certo ou reescrevendo a consulta. No fim do artigo tem um checklist para você seguir hoje mesmo.

Primeiro, a regra: meça antes de mexer

Ao longo da minha experiência com banco de dados, vi muita empresa trocar servidor, aumentar innodb_buffer_pool_size no chute e até migrar de banco por causa de lentidão, quando o problema era uma única consulta rodando milhares de vezes por minuto sem índice.

Ajustar parâmetro sem saber qual consulta está pesando é como trocar o motor do carro por causa de um pneu murcho. Então a ordem é esta:

  1. Achar as consultas que mais consomem tempo no total (não só as mais lentas).
  2. Entender por que cada uma está lenta.
  3. Corrigir e medir de novo.

Bora fazer isso passo a passo.

Passo 1: ligue o slow query log

O slow query log registra toda consulta que passar de um tempo-limite. Dá para ligar sem reiniciar o servidor:

-- Liga o log de consultas lentas em tempo de execução
SET GLOBAL slow_query_log = 1;

-- Tempo-limite em segundos (comece com 1 e vá baixando para 0.5, 0.2...)
SET GLOBAL long_query_time = 1;

-- Recurso do MariaDB: grava o plano de execução junto com a consulta
SET GLOBAL log_slow_verbosity = 'query_plan,explain';

-- Opcional: registra também consultas que não usam índice (gera volume!)
SET GLOBAL log_queries_not_using_indexes = 1;

Atenção: long_query_time alterado com SET GLOBAL só vale para conexões novas. Se a sua aplicação usa pool de conexões, pode levar um tempo até tudo passar a ser registrado.

Para deixar permanente, coloque no arquivo de configuração:

[mariadb]
slow_query_log       = 1
slow_query_log_file  = /var/log/mysql/mariadb-slow.log
long_query_time      = 1
log_slow_verbosity   = query_plan,explain

Curiosidade útil: a partir do 10.11, essas variáveis ganharam nomes novos e mais coerentes (log_slow_query, log_slow_query_file, log_slow_query_time). Os nomes antigos continuam funcionando como apelidos, por isso usei eles aqui: confira a disponibilidade e os apelidos na versão que você usa.

O diferencial está no log_slow_verbosity = explain. No MariaDB, o log já traz o EXPLAIN de cada consulta lenta. Você não precisa reproduzir a consulta para ver o plano que ela usou naquele momento.

Passo 2: veja o que está rodando agora

Se o sistema está lento neste momento, não dá para esperar o log encher. Veja quem está rodando:

-- Lista as conexões e o que cada uma está executando
SHOW FULL PROCESSLIST;

Achou uma consulta rodando há minutos? A partir do 10.9, o MariaDB deixa você ver o plano real dela enquanto ela ainda executa, com contadores de linhas lidas até ali:

-- Substitua 1234 pelo Id que aparece no PROCESSLIST
SHOW ANALYZE FOR 1234;

Isso é ouro em incidente: você descobre por que a consulta está travada sem precisar matá-la e rodar de novo.

Passo 3: ranqueie o que pesa no total

Uma consulta de 8 segundos que roda uma vez por dia incomoda menos do que uma de 200 ms que roda 50 mil vezes por hora. Por isso você precisa agrupar o log por "formato" de consulta e somar o tempo.

Duas ferramentas resolvem:

# Já vem com o MariaDB: top 10 por tempo agregado (confira o efeito do lock time)
mariadb-dumpslow -s t -t 10 /var/log/mysql/mariadb-slow.log

# Mais completa (Percona Toolkit): relatório por impacto
pt-query-digest /var/log/mysql/mariadb-slow.log > relatorio-lentas.txt

O pt-query-digest entrega um ranking com tempo total, número de execuções, linhas examinadas e linhas retornadas. Olhe com carinho para a razão linhas examinadas ÷ linhas retornadas: se a consulta lê 2 milhões de linhas para devolver 20, achamos um candidato.

Passo 4: EXPLAIN mostra o plano, ANALYZE mostra a verdade

Vamos a um caso típico. Uma tela de "pedidos pagos do dia" que ficou lenta conforme a tabela cresceu:

SELECT id, cliente_id, total
FROM pedidos
WHERE DATE(criado_em) = '2026-11-03'
  AND status = 'pago'
ORDER BY criado_em DESC;

O EXPLAIN mostra o que o otimizador pretende fazer, com base em estimativas. O ANALYZE do MariaDB vai além: ele executa a consulta e coloca, lado a lado com a estimativa, o que aconteceu de verdade.

ANALYZE SELECT id, cliente_id, total
FROM pedidos
WHERE DATE(criado_em) = '2026-11-03'
  AND status = 'pago'
ORDER BY criado_em DESC;

As colunas que importam são estas (valores ilustrativos):

Coluna Valor O que significa
type ALL Leu a tabela inteira (full scan)
rows 2.100.000 Quantas linhas o otimizador estimou
r_rows 2.400.000 Quantas linhas ele leu de verdade
r_filtered 0.04 Só 0,04% das linhas lidas eram úteis
Extra Using where; Using filesort Filtrou depois de ler e ainda ordenou em memória ou disco

Para mais detalhe, inclusive tempo gasto em cada etapa, use ANALYZE FORMAT=JSON SELECT ... e procure os campos r_total_time_ms.

Atenção: ANALYZE executa a instrução de verdade. Até um SELECT pode consumir bastante CPU e I/O, adquirir locks conforme a consulta ou chamar funções com efeitos colaterais. ANALYZE UPDATE e ANALYZE DELETE alteram dados: prefira EXPLAIN em produção e faça a medição real numa cópia de teste. Uma transação seguida de ROLLBACK só desfaz alterações transacionais; não desfaz escritas em tabelas não transacionais, inclusive as feitas por triggers, nem efeitos externos. Mesmo com InnoDB, há locks e custo de execução e rollback. Não trate ROLLBACK como botão de “teste sem risco”.

Leitura rápida dos sintomas mais comuns:

O que você vê Causa provável Correção
type = ALL com r_rows alto Falta de índice, ou índice que não pode ser usado Criar o índice certo ou reescrever o filtro
rows muito diferente de r_rows Estatísticas desatualizadas ANALYZE TABLE (veja abaixo)
r_filtered muito baixo Índice pouco seletivo Índice composto com a coluna mais seletiva
Using filesort em muitas linhas ORDER BY sem índice compatível Incluir a coluna do ORDER BY no índice
Using temporary GROUP BY / DISTINCT sem índice Índice nas colunas agrupadas

No nosso exemplo, vale conferir a versão antes de culpar o DATE(). A partir do MariaDB 11.1, comparações de DATE(coluna) e YEAR(coluna) com constantes podem aproveitar índices sobre colunas DATE, DATETIME ou TIMESTAMP: o otimizador transforma essas condições em intervalos. Para versões anteriores, e para deixar o intervalo explícito, a reescrita abaixo continua sendo uma boa opção. O índice composto combina a igualdade em status com o intervalo e a ordenação por criado_em:

-- Índice composto: igualdade primeiro, intervalo/ordenação depois
ALTER TABLE pedidos ADD INDEX idx_status_criado (status, criado_em);

-- Filtro reescrito como intervalo, sem função na coluna
SELECT id, cliente_id, total
FROM pedidos
WHERE status = 'pago'
  AND criado_em >= '2026-11-03'
  AND criado_em <  '2026-11-04'
ORDER BY criado_em DESC;

Rode o ANALYZE de novo e compare. Com esse índice, o otimizador pode usar acesso por intervalo e entregar as linhas na ordem pedida sem filesort, mas a escolha depende dos dados, das estatísticas e do custo estimado. O resultado esperado é ler menos linhas; confirme isso no plano e nos tempos, em vez de assumir que aconteceu.

Passo 5: por que o otimizador escolheu esse plano?

Às vezes o índice existe e o MariaDB mesmo assim não usa. Antes de sair forçando FORCE INDEX, pergunte ao otimizador o que ele pensou:

SET optimizer_trace = 'enabled=on';

SELECT id, cliente_id, total
FROM pedidos
WHERE status = 'pago' AND criado_em >= '2026-11-03';

-- O raciocínio completo do otimizador, em JSON
SELECT TRACE FROM information_schema.OPTIMIZER_TRACE\G

SET optimizer_trace = 'enabled=off';

No trace, procure as seções rows_estimation (quantas linhas ele achou que cada índice traria) e considered_execution_plans (os planos que ele comparou e o custo de cada um). Quase sempre o motivo é estimativa errada, o que nos leva ao próximo ponto.

Estatísticas: o otimizador só é bom quanto os números que ele tem

Se o rows do ANALYZE está muito longe do r_rows, o otimizador está decidindo com informação ruim. O MariaDB tem estatísticas independentes de engine, incluindo histogramas, que dizem como os valores estão distribuídos em uma coluna e não só quantos valores distintos existem:

-- Coleta estatísticas persistentes; pode ser caro em tabelas grandes
ANALYZE TABLE pedidos PERSISTENT FOR ALL;

-- Ou apenas para colunas específicas (mais leve em tabelas grandes)
ANALYZE TABLE pedidos PERSISTENT FOR COLUMNS (status, criado_em) INDEXES ();

Isso faz muita diferença em colunas desbalanceadas. Imagine status, onde 95% dos pedidos estão como entregue e 0,1% como estornado. Com histograma, o otimizador sabe que filtrar por estornado é muito seletivo e que filtrar por entregue não é.

Também vale saber: a partir do MariaDB 11.0, o otimizador passou a usar um modelo de custo novo, calibrado para SSD e com custos mais realistas para cada operação. Se você está em 10.x e sofre com planos ruins em consultas complexas, isso é mais um argumento para planejar o upgrade (veja o artigo sobre o fim de vida do 10.6).

As armadilhas que resolvem 80% dos casos

Estes são os padrões que mais encontro em auditoria de banco:

  • Função na coluna do filtro: pode dificultar o uso do índice, mas não é uma regra absoluta. Desde o 11.1, DATE() e YEAR() têm otimizações para comparações com constantes sobre colunas de data indexadas. Confira a versão e o plano; quando necessário, reescreva como intervalo ou use uma coluna virtual indexada.
  • Conversão implícita de tipo: WHERE cpf = 12345678900 com cpf sendo VARCHAR. O número força conversão linha a linha e o índice é ignorado. Use WHERE cpf = '12345678900'.
  • Ordem errada no índice composto: colunas de igualdade primeiro, depois a de intervalo ou ordenação. Um índice (criado_em, status) não ajuda o nosso exemplo tanto quanto (status, criado_em).
  • LIKE '%texto': curinga no início impede índice B-tree. Para busca textual, use índice FULLTEXT.
  • Paginação com OFFSET gigante: LIMIT 20 OFFSET 500000 lê e descarta 500 mil linhas. Troque por paginação por chave:
-- Em vez de OFFSET, continue a partir do último id da página anterior
SELECT id, cliente_id, total
FROM pedidos
WHERE id < 987654          -- último id exibido na página anterior
ORDER BY id DESC
LIMIT 20;

Para um campo dentro de JSON, o MariaDB permite criar uma coluna virtual e indexá-la. O valor virtual não é armazenado como uma coluna comum na linha, mas o índice ocupa espaço em disco e precisa ser mantido nas escritas. Não é índice de graça:

-- Coluna virtual: sem valor persistido na linha; o índice ocupa espaço
ALTER TABLE clientes
  ADD COLUMN cpf_json VARCHAR(14)
      AS (JSON_VALUE(dados, '$.cpf')) VIRTUAL,
  ADD INDEX idx_cpf_json (cpf_json);

-- Agora a busca usa índice
SELECT id, nome FROM clientes WHERE cpf_json = '123.456.789-00';

Dois recursos do MariaDB que pouca gente conhece

1. Descobrir índices que ninguém usa. Índice demais também deixa o banco lento, porque cada INSERT e UPDATE precisa atualizar todos eles. O MariaDB tem estatísticas de uso nativas:

-- Liga a coleta de estatísticas de uso (overhead baixo)
SET GLOBAL userstat = 1;

-- Depois de alguns dias de uso normal: quais índices foram lidos?
SELECT table_schema, table_name, index_name, rows_read
FROM information_schema.INDEX_STATISTICS
ORDER BY rows_read DESC;

Índice que não aparece nessa lista depois de um ciclo completo de uso (inclua o fechamento do mês!) é candidato à remoção.

2. Testar a remoção sem remover. Apagar um índice de uma tabela grande e descobrir que alguma consulta dependia dele é um pesadelo, porque recriar leva tempo. Desde o 10.6, o MariaDB tem índices ignorados: o índice continua existindo e sendo atualizado, mas o otimizador finge que ele não está lá.

-- O otimizador passa a ignorar o índice (operação instantânea)
ALTER TABLE pedidos ALTER INDEX idx_antigo IGNORED;

-- Algo piorou? Volte na hora, sem reconstruir nada
ALTER TABLE pedidos ALTER INDEX idx_antigo NOT IGNORED;

Deixe o índice ignorado por alguns dias. Se nada piorar, aí sim faça o DROP INDEX com segurança.

O que isso significa para você

Checklist para aplicar hoje:

  • Ligue o slow query log com log_slow_verbosity = query_plan,explain.
  • Depois de 24 a 48 horas, rode o pt-query-digest e pegue as 5 consultas com maior tempo total.
  • Para cada uma, rode ANALYZE e compare rows com r_rows.
  • Corrija com índice composto, reescrita do filtro ou atualização de estatísticas.
  • Meça de novo. Se o tempo total da consulta não caiu, a correção não funcionou.
  • Uma vez por trimestre, revise índices sem uso com userstat e teste a remoção com IGNORED.

E um lembrete: antes de mexer em índice de tabela grande em produção, garanta que você tem um backup que restaura de verdade. Falo disso em detalhe no artigo sobre backup sem parar o banco com mariadb-backup.

Quer ir mais fundo no motor? Veja como o MariaDB lida com milhares de conexões e como uma única correção deixou o 11.8 até 3x mais rápido:


Leia também

Todos os artigos sobre MariaDB →

Referências oficiais

Nota técnica: os exemplos foram revisados com a documentação, mas não executados em um servidor MariaDB nesta publicação. Valide versão, caminhos, privilégios e comportamento em laboratório antes de usar em produção. As capas são ilustrações fotográficas geradas por IA, não resultados de testes.

Continue essa conversa

Quer trocar experiências sobre MariaDB? Entre para a Comunidade MariaDB Brasil.

Me acompanhe no LinkedIn e no Instagram. Para fortalecer sua base em SQL, conheça meu livro Aprendendo MySQL. Conheça também a HTI Tecnologia.