Como identificar consultas SQL lentas em aplicações .NET

Como identificar consultas SQL lentas em aplicações .NET
Publicado em 26/10/2026 por Admin

O desempenho de uma aplicação .NET depende de diversos fatores, incluindo a qualidade do código, a arquitetura da solução, a infraestrutura e a eficiência das operações de acesso a dados. Entre esses fatores, as consultas SQL merecem atenção especial, pois uma consulta lenta pode comprometer o tempo de resposta de uma API, aumentar o consumo de recursos e prejudicar a experiência dos utilizadores.

Em aplicações que utilizam SQL Server, Entity Framework Core ou Dapper, identificar consultas problemáticas é uma etapa fundamental para melhorar o desempenho sem recorrer desnecessariamente a mais recursos de infraestrutura.

O problema é que nem sempre uma consulta lenta é evidente no código C#. Uma operação aparentemente simples pode gerar SQL ineficiente, executar múltiplas consultas ao banco de dados ou provocar leituras desnecessárias de milhares de registos.

Neste artigo, vamos explorar como identificar consultas SQL lentas em aplicações .NET, quais ferramentas utilizar e como investigar a origem dos problemas antes de implementar uma solução.

1. Por que as consultas SQL ficam lentas?

Antes de iniciar a investigação, é importante compreender que existem diferentes causas para o baixo desempenho de uma consulta.

Entre os problemas mais comuns estão:

  • Ausência de índices adequados.

  • Consultas que devolvem mais dados do que o necessário.

  • Utilização inadequada de joins.

  • Execução repetitiva de consultas dentro de ciclos.

  • Problemas de bloqueio e concorrência no banco de dados.

  • Planos de execução ineficientes.

  • Estatísticas desatualizadas.

  • Diferenças nos valores dos parâmetros que influenciam o plano escolhido pelo SQL Server.

  • Latência na comunicação entre a aplicação e o banco de dados.

Também é importante distinguir o tempo gasto na execução da consulta do tempo total de resposta de uma operação.

Uma API pode demorar dois segundos a responder sem que o SQL Server tenha executado uma consulta durante dois segundos. O tempo adicional pode estar relacionado com serialização, processamento de dados, chamadas a outros serviços ou espera por ligações disponíveis no connection pool.

Por isso, a investigação deve começar com medições, e não com suposições.

2. Comece por medir o tempo de execução das consultas

O primeiro passo consiste em descobrir quais operações de acesso a dados estão a consumir mais tempo.

Se a aplicação utiliza Entity Framework Core, uma das formas mais simples de obter essa informação é através do sistema de logging.

Configurar o logging no Entity Framework Core

O Entity Framework Core permite registar os comandos enviados ao banco de dados, incluindo informações sobre a duração da execução.

Por exemplo, numa aplicação que utiliza o sistema de logging do .NET, podemos configurar o registo de comandos SQL da seguinte forma:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options
        .UseSqlServer(
            builder.Configuration
                .GetConnectionString("DefaultConnection"))
        .LogTo(
            Console.WriteLine,
            new[] { DbLoggerCategory.Database.Command.Name },
            LogLevel.Information);
});

Neste exemplo, AppDbContext representa o contexto da aplicação e DefaultConnection corresponde ao nome da connection string configurada.

Durante o desenvolvimento, o logging permite observar os comandos SQL executados e o tempo reportado pelo Entity Framework Core.

Uma entrada de log pode apresentar informações semelhantes a estas:

Executed DbCommand (850ms)
[Parameters=[@__status_0='?' (DbType = Int32)],
 CommandType='Text',
 CommandTimeout='30']

SELECT [o].[Id], [o].[Status], [o].[Total]
FROM [Orders] AS [o]
WHERE [o].[Status] = @__status_0

Neste exemplo ilustrativo, o comando demorou 850 milissegundos.

Esse valor não significa automaticamente que a consulta esteja mal construída. É necessário considerar a quantidade de dados devolvidos, a frequência de execução, o contexto da operação e os requisitos de desempenho da aplicação.

Uma consulta que demora 850 milissegundos e é executada uma vez por dia pode ter um impacto muito diferente de outra com a mesma duração que é executada centenas de vezes por minuto.

Atenção ao logging em produção

O registo detalhado de comandos SQL deve ser utilizado com cuidado em ambientes de produção.

Um volume elevado de logs pode aumentar o consumo de recursos e dificultar a análise dos eventos realmente importantes.

Além disso, os parâmetros das consultas podem conter informações sensíveis, dependendo da aplicação e da configuração de logging.

Uma abordagem mais segura consiste em utilizar logs estruturados, níveis de registo apropriados e ferramentas de observabilidade para recolher métricas sem armazenar indiscriminadamente todos os detalhes das consultas.

Documentação oficial: Diagnóstico de desempenho no Entity Framework Core.

3. Identifique consultas que são executadas repetidamente

Nem sempre o principal problema está numa consulta individualmente lenta. Em muitos casos, a aplicação executa a mesma consulta várias vezes durante uma única operação.

Este cenário é frequente quando uma aplicação carrega uma lista de entidades e, posteriormente, consulta a base de dados para obter informações adicionais de cada elemento.

Exemplo de uma implementação problemática

Imagine uma aplicação que apresenta uma lista de encomendas e o nome do cliente associado a cada uma.

Uma implementação simplificada poderia ser:

var orders = await dbContext.Orders
    .AsNoTracking()
    .ToListAsync();

foreach (var order in orders)
{
    var customer = await dbContext.Customers
        .FirstOrDefaultAsync(c => c.Id == order.CustomerId);

    Console.WriteLine(
        $"{order.Id} - {customer?.Name}");
}

O código executa uma consulta para obter as encomendas e, posteriormente, pode executar uma consulta adicional por encomenda.

Se existirem 500 encomendas, o padrão pode resultar em 501 consultas ao banco de dados.

O número exato de comandos depende dos dados e do comportamento da aplicação, mas a estrutura do código evidencia o problema: o acesso à base de dados ocorre repetidamente dentro do ciclo.

Este padrão é frequentemente associado ao problema N+1.

Uma alternativa mais eficiente

Podemos obter os dados necessários através de uma única consulta com um JOIN, recorrendo a uma projeção:

var orders = await (
    from order in dbContext.Orders.AsNoTracking()
    join customer in dbContext.Customers.AsNoTracking()
        on order.CustomerId equals customer.Id
    select new
    {
        order.Id,
        CustomerName = customer.Name
    })
    .ToListAsync();

Desta forma, o Entity Framework Core pode gerar uma consulta SQL que combina as informações das duas tabelas.

A vantagem não está apenas na quantidade de comandos executados. Também reduzimos as viagens entre a aplicação e o banco de dados, o que pode ser particularmente relevante quando existe latência de rede.

Naturalmente, a melhor solução depende dos requisitos da operação e da relação entre as entidades. O objetivo é evitar consultas desnecessárias, não transformar todas as operações numa única consulta complexa.

4. Utilize o SQL Server Management Studio para investigar consultas

Quando identificamos uma consulta suspeita, o passo seguinte consiste em analisá-la diretamente no banco de dados.

O SQL Server Management Studio (SSMS) disponibiliza ferramentas que ajudam a compreender o comportamento das consultas.

Entre as funcionalidades mais úteis estão:

  • Plano de execução real.

  • Estatísticas de tempo e de I/O.

  • Activity Monitor.

  • Query Store.

  • Ferramentas de diagnóstico de sessões e bloqueios.

Analise o plano de execução real

O plano de execução mostra como o SQL Server decidiu executar uma consulta.

Para uma consulta identificada nos logs da aplicação:

  1. Copie o SQL gerado pelo Entity Framework Core ou utilizado pelo Dapper.

  2. Abra uma nova janela de consulta no SSMS.

  3. Utilize parâmetros equivalentes aos da operação original.

  4. Ative o plano de execução real.

  5. Execute a consulta e analise os operadores apresentados.

O plano permite investigar se o SQL Server está a utilizar índices, a efetuar leituras extensas ou a executar operações dispendiosas.

Por exemplo, uma leitura de tabela ou de índice que envolve uma grande quantidade de registos pode indicar que o banco de dados está a examinar mais dados do que seria desejável.

No entanto, uma operação Index Scan não representa necessariamente um problema. Se a consulta precisar de ler uma parte significativa da tabela, percorrer um índice pode ser a decisão mais eficiente.

Da mesma forma, a presença de um Index Seek não garante que a consulta tenha um bom desempenho.

É necessário analisar o plano completo, a quantidade de linhas processadas, as leituras realizadas e o custo da operação no contexto da carga de trabalho.

Compare as linhas estimadas com as linhas reais

Uma das verificações mais importantes consiste em comparar a quantidade de linhas estimadas pelo otimizador com a quantidade efetivamente processada.

Diferenças significativas podem indicar que as estimativas do SQL Server não representam adequadamente os dados existentes.

Entre as possíveis causas estão estatísticas desatualizadas, distribuições de dados pouco uniformes e condições de filtragem difíceis de estimar.

Essas diferenças podem influenciar a escolha de índices, o tipo de join e a quantidade de recursos utilizados.

A correção depende da causa identificada. Atualizar estatísticas, rever os predicados ou ajustar a consulta pode ser apropriado em determinados cenários, mas nenhuma dessas ações deve ser aplicada automaticamente sem investigação.

5. Utilize o Query Store para encontrar consultas problemáticas

O Query Store é uma das ferramentas mais úteis para investigar problemas de desempenho no SQL Server.

Disponível a partir do SQL Server 2016, permite armazenar informações sobre consultas, planos de execução e estatísticas de execução ao longo do tempo.

Ao contrário de uma análise limitada ao momento em que o problema acontece, o Query Store permite comparar o comportamento de uma consulta entre diferentes períodos.

Isso é particularmente importante quando uma aplicação funcionava corretamente e, sem alterações evidentes no código, começou a apresentar tempos de resposta superiores.

O que pode descobrir com o Query Store?

Com esta funcionalidade, é possível investigar:

  • Consultas com maior duração média.

  • Consultas com elevado consumo de CPU.

  • Operações que realizam muitas leituras.

  • Consultas executadas com elevada frequência.

  • Alterações nos planos de execução.

  • Degradações de desempenho ocorridas ao longo do tempo.

Por exemplo, uma consulta pode ter passado de 100 milissegundos para 1,5 segundos após uma alteração no plano de execução.

Ao comparar o histórico, é possível identificar a regressão e investigar a causa.

No SSMS, as vistas do Query Store ajudam a localizar consultas com problemas de desempenho, incluindo as que apresentam degradação recente.

Atenção: o Query Store tem de estar configurado e a recolher os dados necessários para que exista histórico disponível. A retenção, o espaço utilizado e as opções de captura devem ser ajustados às características do ambiente.

Documentação oficial: Monitorizar o desempenho utilizando o Query Store.

6. Investigue bloqueios e tempos de espera

Uma consulta pode demorar muito tempo a concluir sem estar a consumir CPU continuamente.

Uma causa possível é o bloqueio por outras transações.

Imagine uma transação que atualiza vários registos e permanece aberta durante demasiado tempo. Outras operações que necessitam de aceder aos mesmos recursos podem ter de aguardar.

Do ponto de vista da aplicação .NET, o resultado pode parecer uma consulta lenta, embora o problema esteja relacionado com a concorrência no banco de dados.

Entre os sintomas possíveis estão:

  • Aumento repentino dos tempos de resposta.

  • Consultas que ficam suspensas durante períodos prolongados.

  • Operações que funcionam normalmente em determinados horários, mas ficam lentas noutros.

  • Timeouts que aparecem sobretudo durante períodos de maior atividade.

Para investigar estes cenários, é importante observar as sessões ativas, os bloqueios e as estatísticas de espera do SQL Server.

O Query Store também pode ajudar a identificar categorias de espera associadas a consultas, desde que a recolha relevante esteja configurada e seja suportada pela versão e pelo ambiente utilizados.

A solução pode envolver reduzir a duração das transações, evitar manter transações abertas enquanto se executam chamadas externas ou rever a estratégia de acesso concorrente aos dados.

Aumentar o timeout sem investigar a causa pode apenas prolongar o tempo durante o qual os utilizadores aguardam uma operação.

7. Como investigar consultas lentas com Dapper

O Dapper é uma biblioteca de acesso a dados que permite executar SQL diretamente a partir do código C#.

Como o programador controla explicitamente as instruções SQL, a investigação começa frequentemente pela consulta enviada à base de dados.

Por exemplo:

using Dapper;
using Microsoft.Data.SqlClient;

const string sql = """
    SELECT Id, Name, Email
    FROM Customers
    WHERE Email = @Email;
    """;

await using var connection =
    new SqlConnection(connectionString);

var customer = await connection.QuerySingleOrDefaultAsync<Customer>(
    sql,
    new { Email = email });

Neste exemplo, o parâmetro @Email é enviado separadamente do texto SQL.

Para identificar problemas de desempenho, deve medir-se a duração da operação e correlacioná-la com a consulta executada no SQL Server.

Também é importante distinguir o tempo total da chamada ao Dapper do tempo de execução do comando no servidor. A abertura da ligação, a espera por recursos, a transferência dos resultados e a materialização dos objetos podem contribuir para a duração total.

Uma boa estratégia consiste em combinar métricas da aplicação com informações recolhidas no SQL Server.

Assim, é possível determinar se o problema está na consulta, na comunicação com o banco de dados ou noutra etapa do processamento.

8. Meça as leituras lógicas e o consumo de recursos

O tempo de execução é uma métrica importante, mas não deve ser analisado isoladamente.

Duas consultas podem apresentar durações semelhantes e, ainda assim, ter impactos muito diferentes sobre o servidor.

No SQL Server, as leituras lógicas representam o acesso a páginas de dados que já se encontram em memória.

Uma consulta que realiza muitas leituras lógicas pode estar a examinar um volume elevado de dados, mesmo quando a duração observada não parece preocupante.

Para investigar uma consulta no SSMS, pode utilizar:

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT Id, Name, Email
FROM Customers
WHERE Email = 'cliente@example.com';

SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;

O exemplo deve ser adaptado ao esquema e aos parâmetros reais da aplicação.

SET STATISTICS IO ON permite analisar as leituras realizadas, enquanto SET STATISTICS TIME ON apresenta informações sobre o tempo de CPU e o tempo decorrido durante a execução.

Em ambientes partilhados, evite executar testes pesados sem avaliar o impacto sobre outras operações.

Também não compare resultados obtidos em condições muito diferentes, como níveis de concorrência distintos ou estados de cache diferentes, sem considerar essas diferenças na interpretação.

9. Como transformar o diagnóstico numa melhoria real

Depois de identificar uma consulta problemática, o próximo passo é definir uma hipótese de melhoria e validá-la através de medições.

Um processo estruturado pode seguir estas etapas:

Passo 1 — Registe o comportamento atual

Recolha o tempo de execução, a frequência da consulta, o volume de dados devolvidos e, quando possível, as leituras lógicas e o consumo de CPU.

Passo 2 — Identifique a causa provável

Analise o SQL, os predicados, os joins, os índices existentes, o plano de execução e eventuais bloqueios.

Passo 3 — Escolha uma alteração específica

Dependendo do diagnóstico, a melhoria pode passar por:

  • Criar ou ajustar um índice.

  • Selecionar apenas as colunas necessárias.

  • Reduzir a quantidade de registos devolvidos.

  • Eliminar consultas repetitivas.

  • Reescrever uma condição de filtragem.

  • Rever a utilização de transações.

  • Atualizar estatísticas quando necessário.

Passo 4 — Repita o teste

Execute a consulta com parâmetros e condições comparáveis às do cenário original.

Compare os resultados para verificar se a alteração trouxe uma melhoria real.

Passo 5 — Observe o impacto global

Uma consulta pode ficar mais rápida e, simultaneamente, aumentar o custo de manutenção dos índices ou prejudicar outras operações.

Por isso, avalie também os efeitos sobre as operações de escrita, o consumo de recursos e o comportamento da aplicação sob carga.

10. Boas práticas para prevenir consultas lentas

A investigação reativa é importante, mas a prevenção permite detetar problemas antes que afetem significativamente os utilizadores.

Algumas práticas recomendadas incluem:

  • Monitorizar continuamente os tempos de resposta das APIs.

  • Recolher métricas de duração e frequência das operações de acesso a dados.

  • Utilizar o Query Store para analisar o comportamento das consultas.

  • Evitar carregar entidades e colunas que não são necessárias.

  • Identificar padrões N+1 no Entity Framework Core.

  • Rever os índices à medida que a aplicação evolui.

  • Medir o desempenho antes e depois de alterações importantes.

  • Utilizar testes de carga representativos do ambiente real.

  • Definir limites de desempenho adequados aos requisitos de negócio.

  • Evitar aumentar timeouts sem compreender a origem dos atrasos.

Em aplicações que utilizam Azure, o Application Insights pode ajudar a correlacionar pedidos HTTP, dependências e tempos de resposta, facilitando a identificação de operações de acesso a dados que contribuem para a lentidão.

A observabilidade torna-se ainda mais importante em sistemas distribuídos, nos quais uma operação pode depender de múltiplos serviços e bases de dados.

Conclusão

Identificar consultas SQL lentas em aplicações .NET exige mais do que procurar instruções SQL complexas ou criar índices indiscriminadamente.

O processo começa com a recolha de métricas e a identificação das operações que realmente afetam o desempenho. A partir daí, ferramentas como o logging do Entity Framework Core, o SQL Server Management Studio, os planos de execução e o Query Store ajudam a compreender o comportamento das consultas.

Também é essencial distinguir problemas de execução SQL de situações relacionadas com bloqueios, comunicação, processamento de resultados ou excesso de chamadas ao banco de dados.

A principal recomendação é simples: medir, diagnosticar, alterar e medir novamente.

Com uma abordagem orientada por evidências, é possível melhorar o desempenho das aplicações .NET, reduzir o consumo de recursos e tornar o comportamento do sistema mais previsível, sem introduzir alterações desnecessárias na arquitetura.

Se trabalha com .NET e SQL Server, vale a pena incorporar estas práticas no processo habitual de desenvolvimento e manutenção das suas aplicações.