SQL Server consumindo CPU demais?
Guia completo de diagnóstico e solução — as 6 causas mais comuns, as queries via DMVs e como corrigir cada uma.
CPU em 100% no SQL Server raramente tem uma causa única — e "reiniciar o serviço" nunca é a solução, só um adiamento do próximo incidente. Este guia cobre as causas mais comuns, as queries de diagnóstico que um DBA sênior roda primeiro, e como corrigir cada uma.
FALE COM UM EXPERT SQL SERVER
CPU sustentada em nível crítico?
A HTI faz diagnóstico completo de SQL Server — DMVs, plan cache, waits, MAXDOP e tempdb — com plano de correção priorizado por impacto.
Por onde começar: isolar a causa antes de agir
O erro mais comum sob pressão é começar a mexer em configuração antes de saber o que está de fato consumindo a CPU. As três perguntas que precisam de resposta antes de qualquer ajuste:
- É uma ou poucas queries específicas consumindo CPU, ou é consumo distribuído entre muitas sessões?
- É um pico pontual (job de manutenção, ETL, backup) ou um padrão sustentado ao longo do dia?
- A CPU alta é do processo
sqlservr.exeou de outro processo no mesmo servidor (antivírus, backup de terceiros, outro serviço)?
A query abaixo já responde à primeira pergunta — identifica as queries que mais consumiram CPU desde o último restart do serviço (ou desde a última limpeza do plan cache):
SELECT TOP 20
qs.total_worker_time / 1000 AS total_cpu_ms,
qs.execution_count,
(qs.total_worker_time / qs.execution_count) / 1000 AS avg_cpu_ms,
qs.total_elapsed_time / 1000 AS total_duration_ms,
SUBSTRING(qt.text, (qs.statement_start_offset/2) + 1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset END
- qs.statement_start_offset)/2) + 1) AS query_text,
DB_NAME(qt.dbid) AS database_name
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY qs.total_worker_time DESC;total_worker_time é a coluna que mede tempo de CPU consumido, não total_elapsed_time (que mede tempo total, incluindo espera de I/O e bloqueio) — confundir as duas é um erro comum de diagnóstico.
As 6 causas mais comuns
1. Estatísticas desatualizadas gerando planos de execução ruins
O otimizador de consultas do SQL Server decide o plano de execução com base em estimativas de cardinalidade — que vêm das estatísticas da tabela. Estatísticas desatualizadas (comum após grandes cargas de dados, DELETEs em massa ou AUTO_UPDATE_STATISTICS desabilitado) levam o otimizador a escolher planos ineficientes, frequentemente trocando um seek por um scan completo.
-- Verificar quando as estatísticas foram atualizadas pela última vez
SELECT
OBJECT_NAME(s.object_id) AS table_name,
s.name AS stats_name,
STATS_DATE(s.object_id, s.stats_id) AS last_updated,
sp.rows,
sp.modification_counter
FROM sys.stats s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE OBJECT_NAME(s.object_id) NOT LIKE 'sys%'
ORDER BY sp.modification_counter DESC;2. Parameter sniffing — o plano certo para o parâmetro errado
O SQL Server compila e armazena em cache o plano de execução da primeira chamada de uma stored procedure com parâmetros. Se essa primeira chamada usou um valor atípico (ex: um cliente com volume de pedidos muito acima da média), o plano fica otimizado para aquele caso — e degradado para todos os outros. É a causa clássica de "a procedure roda rápido às vezes e lenta em outras, com os mesmos dados".
-- Forçar recompilação a cada execução (uso pontual para diagnóstico)
EXEC dbo.MinhaProcedure @Parametro = 123 WITH RECOMPILE;
-- Ou, na definição da procedure, para casos recorrentes:
-- OPTION (OPTIMIZE FOR (@Parametro UNKNOWN))3. MAXDOP e Cost Threshold for Parallelism fora do padrão recomendado
Configuração default de MAXDOP = 0 permite que o SQL Server use todos os cores disponíveis para uma única query — o que pode gerar contenção severa em servidores multi-core sob alta concorrência, visível como waits do tipo CXPACKET/CXCONSUMER. A Microsoft recomenda valores específicos de MAXDOP conforme o número de cores e nós NUMA do servidor, quase nunca o default.
-- Verificar configuração atual
EXEC sp_configure 'max degree of parallelism';
EXEC sp_configure 'cost threshold for parallelism';
-- Waits mais comuns associados a paralelismo mal ajustado
SELECT wait_type, wait_time_ms, waiting_tasks_count
FROM sys.dm_os_wait_stats
WHERE wait_type IN ('CXPACKET', 'CXCONSUMER')
ORDER BY wait_time_ms DESC;4. Contenção de tempdb consumindo CPU e gerando espera
Ambientes com um único arquivo de dados no tempdb sofrem contenção de página em estruturas internas (PFS, GAM, SGAM) sob alta concorrência — cada sessão disputando a mesma página gera overhead de CPU adicional, além da espera. A recomendação padrão da Microsoft é múltiplos arquivos de dados de tamanho igual, tipicamente um por core lógico até o limite de 8.
-- Verificar número de arquivos de dados do tempdb hoje
SELECT name, physical_name, size/128 AS size_mb
FROM tempdb.sys.database_files
WHERE type = 0;5. Queries ad hoc não parametrizadas inflando o plan cache
Aplicações que geram SQL dinâmico sem parametrização (concatenando valores diretamente na query) forçam o SQL Server a compilar um plano novo para cada variação — consumindo CPU em compilação que deveria ser gasta em execução. Forced Parameterization no nível do banco pode mitigar isso sem mudança de código, mas o ideal é corrigir na aplicação.
-- Taxa de compilação e recompilação (perfmon counters via DMV)
SELECT cntr_value AS batch_requests_sec
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Batch Requests/sec';
SELECT cntr_value AS sql_compilations_sec
FROM sys.dm_os_performance_counters
WHERE counter_name = 'SQL Compilations/sec';6. Índices ausentes ou fragmentados forçando scans completos
Um índice ausente força o otimizador a escanear a tabela inteira para encontrar as linhas necessárias — trabalho de CPU proporcional ao tamanho da tabela, não ao tamanho do resultado. Índices existentes mas altamente fragmentados têm efeito parecido, em menor escala.
-- Sugestões de índice ausente identificadas pelo próprio otimizador
SELECT
migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) AS improvement_measure,
mid.statement AS table_name,
mid.equality_columns,
mid.inequality_columns,
mid.included_columns
FROM sys.dm_db_missing_index_group_stats migs
JOIN sys.dm_db_missing_index_groups mig ON migs.group_handle = mig.index_group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
ORDER BY improvement_measure DESC;Antes de sair mudando configuração
- Nunca aplique um
WITH RECOMPILEpermanente em produção como solução — é uma ferramenta de diagnóstico, não uma correção. Recompilar toda execução tem custo de CPU próprio. - Não mude MAXDOP no servidor inteiro sem medir o impacto — o valor certo depende do padrão de carga (OLTP vs. Data Warehouse) tanto quanto do hardware.
- Confirme que a CPU alta é do
sqlservr.exeantes de qualquer ajuste — já vimos casos em que o "SQL Server consumindo CPU" era, na verdade, um job de antivírus escaneando os arquivos.mdf/.ldfem tempo real.
Precisa de ajuda para diagnosticar seu ambiente agora?
Se o servidor está com CPU sustentada em nível crítico, não espere o Health Check completo — fale com o time de atendimento emergencial.
FALE COM UM EXPERT SQL SERVER
CPU sustentada em nível crítico?
A HTI faz diagnóstico completo de SQL Server — DMVs, plan cache, waits, MAXDOP e tempdb — com plano de correção priorizado por impacto.
Artigo do cluster técnico Consultoria SQL Server da HTI Tecnologia — 35+ anos sustentando bancos de dados críticos no Brasil.