MySQL ou MariaDB lento? Ative o slow query log, resuma as piores consultas com pt-query-digest e leia o EXPLAIN para saber o que corrigir.
Para que serve
Quando você tem um MySQL lento, a causa quase sempre está em poucas consultas que leem linhas demais, ordenam em disco ou esperam por bloqueios. Este artigo mostra como capturar essas consultas com o slow query log, ordenar as piores e interpretar o EXPLAIN para decidir se falta índice ou se a consulta precisa ser reescrita. Vale para MySQL 8.0/8.4 e MariaDB 10.11/11.x.
Se o problema for memória mal dimensionada (buffer pool pequeno, por exemplo), veja também o artigo sobre tuning básico de MySQL para sites, na categoria Web e hospedagem. Aqui o foco é encontrar a consulta culpada.
Pré-requisitos
- Acesso root ao servidor (ou à VM do banco) e a um usuário MySQL com privilégio administrativo.
- Alguns GB livres no disco onde o log será gravado.
- Opcional: pacote
percona-toolkit(disponível nos repositórios do Debian e do Ubuntu) para opt-query-digest.
1. MySQL lento: confirme que o banco é mesmo o gargalo
- Rode
topouhtope veja se o processomysqld/mariadbdconsome CPU, ou se owa(espera de I/O) está alto. - Veja o que está rodando agora:
Consultas commysql -e "SHOW FULL PROCESSLIST;"Timealto e estadoSending data,Creating sort indexouCopying to tmp tablejá são suspeitas. EstadoWaiting for ... lockindica bloqueio: nesse caso, veja o artigo sobre deadlocks e locks desta categoria.
2. Ative o slow query log do MySQL
Dá para ligar sem reiniciar o serviço:
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
SET GLOBAL log_throttle_queries_not_using_indexes = 60;
long_query_timeé em segundos e aceita frações (ex.:0.5). Comece com 1 segundo; em sistemas OLTP rápidos, 0.2 a 0.5 revela mais.- O novo
long_query_timesó vale para conexões abertas depois do comando. Aplicações com pool de conexões podem demorar a refletir. log_throttle_queries_not_using_indexes(só no MySQL) limita quantas consultas "sem índice" entram por minuto, para o log não explodir. No MariaDB, omita essa linha.
Para manter após reinício, grave no arquivo de configuração. No Debian/Ubuntu: /etc/mysql/mysql.conf.d/mysqld.cnf (MySQL) ou /etc/mysql/mariadb.conf.d/50-server.cnf (MariaDB), dentro de [mysqld]:
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
log_throttle_queries_not_using_indexes = 60
No MySQL 8 também é possível usar SET PERSIST no lugar de SET GLOBAL, que grava o valor em mysqld-auto.cnf. No MariaDB os nomes novos são log_slow_query e log_slow_query_time, mas os nomes antigos continuam funcionando como sinônimos.
long_query_time = 0 por muito tempo em produção. Isso grava todas as consultas e pode encher o disco em poucas horas. Use 0 só por alguns minutos, para amostragem.3. Resuma o log e escolha o que atacar
Deixe o log coletar durante um período representativo (horário de pico). Depois, gere um ranking:
# Ferramenta que vem com o MySQL: top 10 por tempo total
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
# Mais completo: agrupa consultas iguais e mostra percentis
apt install percona-toolkit
pt-query-digest /var/log/mysql/mysql-slow.log > /root/relatorio-slow.txt
No relatório do pt-query-digest, a primeira tabela ("Profile") ordena as consultas pelo tempo total gasto. Priorize por essa coluna: uma consulta de 0,3 s executada 50 mil vezes por hora pesa mais que uma de 20 s executada uma vez. Observe também Rows examine versus Rows sent: examinar 2 milhões de linhas para devolver 10 é sinal clássico de índice ausente.
4. Leia o EXPLAIN da consulta lenta
Copie a consulta do relatório, troque os valores de exemplo por valores reais e coloque EXPLAIN na frente:
EXPLAIN SELECT id, total FROM pedidos
WHERE cliente_id = 4821 AND status = 'aberto'
ORDER BY criado_em DESC LIMIT 20;
As colunas que mais importam:
| Coluna | O que observar |
|---|---|
type | Do pior para o melhor: ALL (varre a tabela inteira), index (varre o índice inteiro), range, ref, eq_ref, const. ALL em tabela grande é o primeiro alvo. |
key | Índice escolhido. NULL significa nenhum. |
possible_keys | Índices candidatos. Se há candidato mas key é NULL, o otimizador achou o índice pouco seletivo. |
rows e filtered | Estimativa de linhas lidas e a porcentagem que sobra após o filtro. rows alto com filtered baixo = desperdício. |
Extra | Using filesort (ordenação fora do índice) e Using temporary (tabela temporária) custam caro em volume. Using index é bom: a consulta foi respondida só pelo índice. |
Para ver o tempo real de cada etapa, use EXPLAIN ANALYZE no MySQL 8 (executa a consulta de verdade) ou ANALYZE FORMAT=JSON SELECT ... no MariaDB. Não rode essas variantes com UPDATE/DELETE em produção sem cuidado, porque elas executam o comando.
5. Corrija a causa
- Índice ausente: no exemplo acima, um índice composto
(cliente_id, status, criado_em)resolve o filtro e a ordenação. Veja o artigo sobre índices: quando criar e como achar os que faltam. - Função na coluna:
WHERE DATE(criado_em) = '2026-05-01'impede o uso de índice. Prefira um intervalo:criado_em >= '2026-05-01' AND criado_em < '2026-05-02'. - Tipos diferentes: comparar coluna
VARCHARcom número (WHERE cpf = 12345678900) força conversão e ignora o índice. Use aspas. LIKE '%texto': curinga no início não usa índice B-tree. Avalie índice FULLTEXT ou outra estratégia de busca.- Paginação com
OFFSETgrande:LIMIT 20 OFFSET 200000lê e descarta 200 mil linhas. Pagine pela chave (WHERE id < último_id ORDER BY id DESC LIMIT 20). SELECT *em tabelas largas: traga só as colunas necessárias; isso também permite índices de cobertura.
Como saber se funcionou
- O
EXPLAINmostratypediferente deALL, um índice emkeyerowsmuito menor. - A consulta some (ou cai de posição) no próximo relatório do
pt-query-digest. - O tempo de resposta da aplicação e o uso de CPU/I/O do servidor caem no horário de pico.
Problemas comuns
O arquivo de log não é criado
O diretório precisa pertencer ao usuário mysql. No Ubuntu, o AppArmor só permite gravação em caminhos previstos no perfil; use /var/log/mysql/. Veja erros em journalctl -u mysql ou journalctl -u mariadb.
O log cresce sem parar
Confira se existe regra em /etc/logrotate.d/ para o arquivo (o pacote do MySQL/MariaDB costuma trazer uma para /var/log/mysql/*.log). Depois da análise, você pode subir long_query_time ou desligar o log.
A consulta é rápida no meu teste, mas lenta na aplicação
Pode ser bloqueio (outra transação segurando linhas), cache frio ou parâmetros diferentes. Teste com os mesmos valores que aparecem no log e confira o processlist no momento da lentidão.
Perguntas frequentes
O slow query log deixa o MySQL mais lento?
Com long_query_time de 0,5 a 1 segundo o impacto é desprezível, porque poucas consultas são gravadas. O custo só aparece com limite 0 em servidores muito movimentados.
Qual o valor ideal de long_query_time?
Comece em 1 segundo para achar os casos graves. Depois de corrigi-los, desça para 0,2 a 0,5 para pegar as consultas frequentes que somam muito tempo.
EXPLAIN funciona com UPDATE e DELETE?
Sim. EXPLAIN UPDATE ... mostra o plano sem alterar dados. Só EXPLAIN ANALYZE executa de fato o comando.
Existe alternativa ao arquivo de log?
Sim: o Performance Schema guarda estatísticas por tipo de consulta. Veja o artigo sobre monitorar banco de dados desta categoria.
Leitura complementar
Precisa de ajuda?
Se o servidor continuar lento mesmo após essas verificações, abra um ticket informando o horário do problema e anexando o relatório do pt-query-digest. A otimização do banco é responsabilidade do cliente sem o serviço de Gerenciamento, mas podemos verificar se há problema de hardware, disco ou rede.
