Queries lentas são as assassinas silenciosas do desempenho de bancos de dados. Uma única query não otimizada executando milhares de vezes por dia pode derrubar um servidor MySQL que de outra forma funcionaria bem. O slow query log do MySQL é sua primeira linha de defesa — ele captura cada query que excede um limite de tempo, fornecendo os dados necessários para identificar e eliminar gargalos de desempenho. Combinado com ferramentas de análise como mysqldumpslow e pt-query-digest, você pode encontrar, priorizar e corrigir sistematicamente as piores ofensoras na sua carga de trabalho.

Pré-requisitos

  • MySQL 5.7+ ou MySQL 8.0 (comandos funcionam em ambos; diferenças menores indicadas)
  • Acesso root ou administrativo ao servidor MySQL
  • Acesso shell ao servidor executando MySQL
  • Percona Toolkit instalado (para pt-query-digest)
  • Conhecimento básico de SQL e arquivos de configuração do MySQL

Ativando o Slow Query Log

O slow query log está desativado por padrão na maioria das instalações do MySQL. Você pode ativá-lo permanentemente via configuração ou temporariamente em tempo de execução.

Configuração Permanente (my.cnf)

Adicione estas linhas ao seu arquivo de configuração do MySQL, normalmente em /etc/mysql/my.cnf ou /etc/my.cnf:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_output = FILE

Reinicie o MySQL para aplicar:

sudo systemctl restart mysql

Configuração em Tempo de Execução (Sem Reinício)

Ative o slow query log sem reiniciar o servidor:

SET GLOBAL slow_query_log = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_output = 'FILE';

Verifique as configurações:

SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

Saída esperada:

+---------------------+-------------------------------+
| Variable_name       | Value                         |
+---------------------+-------------------------------+
| slow_query_log      | ON                            |
| slow_query_log_file | /var/log/mysql/slow.log       |
+---------------------+-------------------------------+

Opções de log_output

O MySQL suporta três destinos de saída:

  • FILE — grava no arquivo especificado por slow_query_log_file (padrão, recomendado)
  • TABLE — grava na tabela mysql.slow_log (consultável mas adiciona overhead)
  • NONE — desativa o registro apesar de slow_query_log=1

Use FILE para produção. A opção TABLE adiciona overhead mensurável de escrita e faz o schema mysql crescer de forma imprevisível.

Configurando Parâmetros do Log

long_query_time

Este limite determina quais queries são registradas. O padrão de 10 segundos é muito alto para a maioria das cargas de trabalho.

-- Registrar queries que levem mais de 0.5 segundos
SET GLOBAL long_query_time = 0.5;

-- Registrar TODAS as queries (útil para auditorias curtas, não para produção)
SET GLOBAL long_query_time = 0;

Valores iniciais recomendados:

Ambientelong_query_timeJustificativa
Desenvolvimento0Captura tudo para análise
Staging0.1Detecta queries >100ms
Produção1.0Foco em queries claramente lentas
Agressivo0.5Equilíbrio entre ruído e cobertura

log_queries_not_using_indexes

Este parâmetro registra queries que fazem varreduras completas de tabela, independente do tempo de execução:

SET GLOBAL log_queries_not_using_indexes = 1;

É extremamente valioso para detectar queries que são rápidas agora mas degradarão conforme as tabelas crescerem. Porém, pode gerar muitas entradas de log em bancos com muitas tabelas pequenas.

min_examined_row_limit

Filtra queries triviais exigindo um número mínimo de linhas examinadas:

SET GLOBAL min_examined_row_limit = 1000;

Isso evita que o log se encha com buscas rápidas de uma única linha que não usam índice.

Análise com mysqldumpslow

O mysqldumpslow vem incluído com o MySQL e não requer instalação adicional. Ele faz parsing do slow query log, abstrai valores literais e agrupa queries semelhantes.

Uso Básico

# Top 10 queries por tempo total
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# Top 10 queries por frequência
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log

# Top 10 queries por tempo médio
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log

# Filtrar queries que correspondam a um padrão
mysqldumpslow -s t -t 10 -g "orders" /var/log/mysql/slow.log

Opções de Ordenação

FlagOrdena por
-s tTempo total
-s cFrequência (contagem)
-s atTempo médio
-s lTempo de lock
-s alTempo médio de lock
-s rLinhas examinadas
-s arMédia de linhas examinadas

Exemplo de Saída

Count: 1523  Time=2.45s (3731s)  Lock=0.00s (1s)  Rows=245.3 (373534), root[root]@localhost
  SELECT * FROM orders WHERE customer_id = N AND status = 'S' ORDER BY created_at DESC LIMIT N;

Isso indica que a query foi executada 1.523 vezes, com média de 2,45 segundos cada, examinando 245 linhas por execução e consumindo 3.731 segundos no total.

Limitações

O mysqldumpslow é útil para verificações rápidas mas tem limitações significativas:

  • Sem análise de percentis (P95, P99)
  • Sem distribuição de tempo de resposta
  • Não pode ler logs em formato binário ou TABLE
  • Opções limitadas de filtragem e relatórios

Análise Aprofundada com pt-query-digest

O pt-query-digest do Percona Toolkit é o padrão da indústria para análise de slow query logs. Fornece informações muito mais detalhadas que o mysqldumpslow.

Instalação

# Debian/Ubuntu
sudo apt-get install percona-toolkit

# RHEL/CentOS
sudo yum install percona-toolkit

# A partir do código fonte
wget https://www.percona.com/downloads/percona-toolkit/LATEST/tarball/percona-toolkit-LATEST.tar.gz
tar xzf percona-toolkit-LATEST.tar.gz
cd percona-toolkit-* && perl Makefile.PL && make && sudo make install

Uso Básico

# Relatório completo
pt-query-digest /var/log/mysql/slow.log

# Relatório das últimas 24 horas
pt-query-digest --since '24h' /var/log/mysql/slow.log

# Filtrar por banco de dados
pt-query-digest --filter '$event->{db} eq "myapp"' /var/log/mysql/slow.log

# Saída para arquivo
pt-query-digest /var/log/mysql/slow.log > /tmp/slow-report.txt

Estrutura do Relatório

A saída possui três seções:

1. Resumo Geral

# 2.1M QPS, 1.2x concurrency, 45% of time in query
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time         3731s    100ms     45s   2.45s   4.12s   1.33s   1.85s
# Lock time            1s       0     15ms    10us    22us    38us     8us
# Rows sent        373534       0    1000     245   480.0   180.3   198.0
# Rows examine     2.1M         0   50000    1382    3200    2100     850

2. Perfil (Ranking de Queries)

# Rank Query ID                         Response time  Calls  R/Call  Item
# ==== ================================ ============== ====== ======= ====
#    1 0xE77769C62EF669AA1A6...          1800.0 48.2%   1523  1.1819  SELECT orders
#    2 0xA4B9D8C1F3E5A7B2C9...           950.3 25.5%    892  1.0654  SELECT products
#    3 0xF1C2D3E4A5B6C7D8E9...           480.1 12.9%   3201  0.1500  SELECT users

3. Detalhe por Query — para cada query classificada, você obtém o fingerprint completo, distribuição de tempos, recomendações EXPLAIN e uma query de exemplo com valores literais.

mysqldumpslow vs pt-query-digest vs MySQL Enterprise Monitor vs PMM

Recursomysqldumpslowpt-query-digestMySQL Enterprise MonitorPercona PMM
CustoGrátis (incluído)Grátis (open source)Licença comercialGrátis (open source)
InstalaçãoNenhumaPercona ToolkitServidor dedicadoDocker/VM
Monitoramento em tempo realNãoNãoSimSim
Análise históricaBásicaExcelenteExcelenteExcelente
FingerprintingBásicoAvançadoAvançadoAvançado
Análise de percentisNãoSim (P95, P99)SimSim
Dashboard visualNãoNãoSimSim (Grafana)
Integração EXPLAINNãoParcialSimSim
AlertasNãoNãoSimSim
Ideal paraVerificações rápidasAnálise profundaEquipes enterpriseEquipes que querem UI gratuita

Otimização de Queries Lentas

Uma vez identificadas as queries mais lentas, siga esta abordagem sistemática.

Passo 1: EXPLAIN da Query

EXPLAIN SELECT * FROM orders
WHERE customer_id = 42 AND status = 'pending'
ORDER BY created_at DESC LIMIT 10;

Colunas-chave a examinar:

  • type: Deve ser ref, range ou const. Evite ALL (varredura completa de tabela)
  • key: Qual índice o MySQL escolheu. NULL significa que nenhum índice foi usado
  • rows: Linhas estimadas examinadas. Números altos indicam índices ausentes
  • Extra: Observe Using filesort e Using temporary

No MySQL 8.0, use EXPLAIN ANALYZE para estatísticas reais de execução:

EXPLAIN ANALYZE SELECT * FROM orders
WHERE customer_id = 42 AND status = 'pending'
ORDER BY created_at DESC LIMIT 10;

Passo 2: Adicionar Índices Ausentes

Com base na saída do EXPLAIN, crie índices compostos que cubram as cláusulas WHERE e ORDER BY:

-- Cobre WHERE customer_id AND status, ORDER BY created_at
CREATE INDEX idx_orders_customer_status_created
ON orders (customer_id, status, created_at DESC);

Regras de design de índices:

  • Colunas de igualdade primeiro (customer_id, status)
  • Colunas de range/ordenação por último (created_at)
  • Inclua colunas do SELECT se criar um covering index
  • Evite indexação excessiva — cada índice desacelera escritas

Passo 3: Reescrever Padrões Problemáticos

Reescritas comuns que melhoram o desempenho:

-- RUIM: Subquery executada por linha
SELECT * FROM orders WHERE customer_id IN (
  SELECT id FROM customers WHERE region = 'US'
);

-- BOM: JOIN executa uma vez
SELECT o.* FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE c.region = 'US';

-- RUIM: SELECT * busca colunas não utilizadas
SELECT * FROM orders WHERE id = 42;

-- BOM: Seleciona apenas colunas necessárias
SELECT id, status, total, created_at FROM orders WHERE id = 42;

-- RUIM: Função em coluna indexada impede uso do índice
SELECT * FROM orders WHERE YEAR(created_at) = 2025;

-- BOM: Varredura por range usa o índice
SELECT * FROM orders
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';

Cenário do Mundo Real

Você gerencia o banco de dados de uma plataforma de e-commerce. Usuários relatam que páginas de produtos carregam lentamente durante horários de pico (14h-18h), mas o resto do site funciona bem. Métricas do servidor mostram picos de CPU coincidindo com as reclamações, mas memória e I/O de disco estão normais.

Passo 1: Ative o slow query log com long_query_time=0.5 durante os horários de pico.

Passo 2: Após 2 horas de registro, execute pt-query-digest:

pt-query-digest --since '2h' /var/log/mysql/slow.log

Passo 3: O relatório revela que a query principal consome 62% do tempo total:

SELECT p.*, c.name as category_name,
       (SELECT AVG(rating) FROM reviews WHERE product_id = p.id) AS avg_rating,
       (SELECT COUNT(*) FROM reviews WHERE product_id = p.id) AS review_count
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.category_id = 15 AND p.active = 1
ORDER BY created_at DESC LIMIT 20;

Passo 4: EXPLAIN mostra que as subqueries correlacionadas executam uma vez por linha de produto (2.400 vezes por carregamento de página). Corrija reescrevendo com JOIN:

SELECT p.*, c.name as category_name,
       COALESCE(r.avg_rating, 0) AS avg_rating,
       COALESCE(r.review_count, 0) AS review_count
FROM products p
JOIN categories c ON p.category_id = c.id
LEFT JOIN (
  SELECT product_id, AVG(rating) as avg_rating, COUNT(*) as review_count
  FROM reviews GROUP BY product_id
) r ON r.product_id = p.id
WHERE p.category_id = 15 AND p.active = 1
ORDER BY p.created_at DESC LIMIT 20;

Passo 5: Adicione um índice composto:

CREATE INDEX idx_products_category_active_created
ON products (category_id, active, created_at DESC);

Resultado: O tempo da query cai de 2,4 segundos para 12 milissegundos. O carregamento de páginas volta ao normal.

Armadilhas e Casos Especiais

  • Rotação de logs é essencial. Sem rotação, o slow log pode consumir todo o espaço disponível em disco. Use logrotate ou o comando FLUSH SLOW LOGS do MySQL com um cron job.
  • Monitoramento de espaço em disco. Configurar long_query_time=0 em um servidor movimentado pode gerar gigabytes de dados de log por hora. Sempre monitore o uso de disco ao reduzir o limite.
  • Atraso de replicação pelo logging. Em réplicas com log_slow_replica_statements=1 (MySQL 8.0.26+), registrar queries replicadas pode aumentar o atraso. Monitore Seconds_Behind_Source.
  • Inundação por log_queries_not_using_indexes. Tabelas pequenas de lookup (países, status) acionarão este flag mesmo que varreduras completas em tabelas de 50 linhas sejam ideais. Use min_examined_row_limit para filtrá-las.
  • Prepared statements. Por padrão, prepared statements não são registrados. Configure log_slow_extra=1 (MySQL 8.0.14+) para capturar estatísticas extras.
  • Mudanças em tempo de execução não persistem. Mudanças com SET GLOBAL são perdidas ao reiniciar. Sempre atualize o my.cnf além dos comandos em tempo de execução.

Resolução de Problemas

Arquivo do slow query log não é criado: Verifique se o MySQL tem permissões de escrita no diretório de logs:

sudo mkdir -p /var/log/mysql
sudo chown mysql:mysql /var/log/mysql
sudo chmod 750 /var/log/mysql

Log mostra zero queries apesar de slow_query_log=ON: Verifique se o long_query_time não está configurado muito alto:

SELECT @@global.long_query_time;

Se mostra 10.000000, queries precisam levar mais de 10 segundos para serem registradas. Reduza para 1.

pt-query-digest reporta “No events processed”: O arquivo de log pode estar vazio ou o formato não é reconhecido. Certifique-se de que log_output=FILE (não TABLE). Verifique permissões:

ls -la /var/log/mysql/slow.log
file /var/log/mysql/slow.log

mysqldumpslow mostra “Permission denied”: A ferramenta precisa de acesso de leitura ao arquivo de log. Execute com sudo ou adicione seu usuário ao grupo mysql:

sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

Queries aparecem no slow log mas EXPLAIN mostra execução rápida: Isso geralmente significa contenção de locks. Verifique o campo Lock_time na entrada do log. Tempos altos de lock indicam problemas de locking no nível da tabela ou linha, não problemas de otimização de query.

Resumo

  • Ative o slow query log com slow_query_log=1 e configure long_query_time para 1 segundo ou menos
  • Use log_queries_not_using_indexes para detectar queries que degradarão conforme os dados crescerem
  • Comece com mysqldumpslow para análise rápida; mude para pt-query-digest para análise de nível produção
  • Foque no tempo total (frequência × tempo médio) em vez de apenas a execução mais lenta
  • Use EXPLAIN e EXPLAIN ANALYZE (MySQL 8.0) para entender planos de execução antes de criar índices
  • Projete índices compostos com colunas de igualdade primeiro e colunas de range/ordenação por último
  • Reescreva subqueries correlacionadas como JOINs para melhorias de uma ordem de magnitude
  • Sempre implemente rotação de logs e monitore espaço em disco ao usar o slow query log em produção

Artigos Relacionados