Truques para depurar problemas de performance no MySQL
1. Preparando o Ambiente de Diagnóstico
Antes de qualquer análise, é fundamental configurar o ambiente para capturar informações relevantes sem impacto excessivo no servidor de produção. Comece habilitando o slow_query_log:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
O performance_schema oferece visibilidade granular sobre eventos do servidor. Ative-o no arquivo de configuração (my.cnf) e reinicie o MySQL:
[mysqld]
performance_schema=ON
Para testes controlados, o general_log pode ser útil, mas use com cautela em produção:
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/tmp/mysql-general.log';
2. Analisando Queries com EXPLAIN
O EXPLAIN é a ferramenta mais poderosa para entender como o MySQL executa uma consulta. Exemplo prático:
EXPLAIN SELECT u.nome, COUNT(p.id) as total_pedidos
FROM usuarios u
LEFT JOIN pedidos p ON u.id = p.usuario_id
WHERE u.ativo = 1
GROUP BY u.id;
Interpretação dos campos críticos:
- type: ALL indica scan completo (péssimo), ref ou range são bons
- key: mostra qual índice foi usado (NULL significa sem índice)
- rows: estimativa de linhas examinadas
- Extra: Using where; Using index é ideal; Using filesort indica ordenação sem índice
Para detalhes avançados, use EXPLAIN FORMAT=JSON:
EXPLAIN FORMAT=JSON
SELECT * FROM pedidos WHERE data_criacao BETWEEN '2024-01-01' AND '2024-12-31';
3. Identificando e Otimizando Índices
Índices mal projetados são a causa mais comum de lentidão. Detecte índices redundantes consultando o information_schema:
SELECT table_name, index_name, column_name, seq_in_index
FROM information_schema.statistics
WHERE table_schema = 'meu_banco';
Use SHOW INDEX FROM para ver detalhes de uma tabela específica:
SHOW INDEX FROM pedidos;
Crie índices compostos colocando colunas mais seletivas primeiro. Por exemplo, se filtramos por status (poucos valores) e data_criacao (muitos valores):
CREATE INDEX idx_pedidos_status_data ON pedidos (data_criacao, status);
Teste com EXPLAIN se o índice está sendo utilizado. Se não, considere reordenar as colunas.
4. Monitorando Bloqueios e Deadlocks
Deadlocks podem paralisar aplicações inteiras. Use SHOW ENGINE INNODB STATUS para encontrar a última ocorrência:
SHOW ENGINE INNODB STATUS\G
Procure pela seção LATEST DETECTED DEADLOCK. Para logar todos os deadlocks automaticamente:
SET GLOBAL innodb_print_all_deadlocks = 'ON';
Configure um timeout razoável para bloqueios:
SET GLOBAL innodb_lock_wait_timeout = 50;
Para identificar bloqueios ativos via performance_schema:
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
5. Utilizando Ferramentas de Profiling e Logging
O profiling interno do MySQL permite medir o tempo real de cada etapa:
SET profiling = 1;
SELECT * FROM pedidos WHERE valor > 1000;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
Para análise em massa de logs lentos, o pt-query-digest é indispensável:
pt-query-digest /var/log/mysql/mysql-slow.log > analise_queries.txt
Entenda o volume de operações internas com Handler:
SHOW STATUS LIKE 'Handler%';
Valores altos em Handler_read_rnd_next indicam scans completos desnecessários.
6. Verificando Configurações do Buffer e Cache
O innodb_buffer_pool_size é a configuração mais crítica para performance. Ajuste-o para 70-80% da RAM disponível em servidores dedicados:
SET GLOBAL innodb_buffer_pool_size = 8 * 1024 * 1024 * 1024; -- 8GB
Monitore a eficiência do buffer pool:
SHOW ENGINE INNODB STATUS\G
Procure por Buffer pool hit rate — abaixo de 95% indica necessidade de aumentar o buffer.
O Query Cache foi removido no MySQL 8.0, mas em versões anteriores pode causar contenção. Desabilite-o:
SET GLOBAL query_cache_type = 0;
Ajuste a memória temporária para evitar gravação em disco:
SET GLOBAL tmp_table_size = 64 * 1024 * 1024; -- 64MB
SET GLOBAL max_heap_table_size = 64 * 1024 * 1024;
7. Otimizando Consultas com Estratégias Avançadas
Subconsultas podem ser lentas. Reescreva-as como JOIN:
-- Lento
SELECT * FROM usuarios WHERE id IN (SELECT usuario_id FROM pedidos WHERE valor > 1000);
-- Rápido
SELECT DISTINCT u.* FROM usuarios u
JOIN pedidos p ON u.id = p.usuario_id
WHERE p.valor > 1000;
Para testar planos alternativos temporariamente:
SELECT * FROM pedidos FORCE INDEX (idx_data) WHERE data_criacao > '2024-01-01';
Evite filesort ordenando por colunas indexadas:
-- Pode causar filesort
SELECT * FROM pedidos ORDER BY data_criacao DESC, valor ASC;
-- Otimizado se existir índice (data_criacao, valor)
-- A ordenação deve seguir a ordem do índice
Use LIMIT com índices para evitar scans desnecessários:
SELECT id, nome FROM usuarios ORDER BY id LIMIT 1000, 20;
Com índice em id, o MySQL evita examinar todas as linhas.
Referências
- Documentação oficial do MySQL: EXPLAIN — Guia completo sobre interpretação de planos de execução
- MySQL Performance Schema Documentation — Referência oficial para monitoramento de eventos e bloqueios
- Percona Toolkit: pt-query-digest — Ferramenta para análise avançada de logs lentos
- MySQL Indexing Best Practices — Tutorial prático sobre criação e otimização de índices
- MySQL InnoDB Locking and Deadlocks — Documentação oficial sobre bloqueios e deadlocks no InnoDB
- MySQL Server System Variables — Referência completa para ajuste de buffer pool, cache e timeout