Consulta do Entity Framework lenta, mas o mesmo SQL em SqlQuery é rápido


93

Estou vendo um desempenho realmente estranho relacionado a uma consulta muito simples usando o Entity Framework Code-First com .NET framework versão 4. A consulta LINQ2Entities se parece com isto:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Isso leva mais de 3.000 milissegundos para ser executado. O SQL gerado parece muito simples:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Essa consulta é executada quase instantaneamente quando executada por meio do Management Studio. Quando eu altero o código C # para usar a função SqlQuery, ele é executado em 5 a 10 milissegundos:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Portanto, exatamente o mesmo SQL, as entidades resultantes são rastreadas por alterações em ambos os casos, mas uma grande diferença de desempenho entre os dois. O que da?


2
Espero que você esteja vendo atrasos na inicialização - provavelmente, veja a compilação. Consulte MSDN:Performance Considerations for Entity Framework 5
— Nicholas Butler,

Eu tentei pré-gerar visualizações e não parece ajudar. Além disso, executei outra consulta EF antes da lenta para descartar o material de inicialização. A nova consulta foi executada rapidamente, a lenta ainda funcionou lentamente, embora o aquecimento do contexto tenha ocorrido durante a primeira consulta.
— Brian Sullivan,

1
@marc_s - Não, SqlQuery retornará uma instância de entidade totalmente materializada e com controle de alterações. Consulte msdn.microsoft.com/en-us/library/…
— Brian Sullivan

O SQL gerado para sua consulta EF está realmente embutindo o valor do parâmetro ou usando um parâmetro? Isso não deve afetar a velocidade de consulta para uma consulta individual, mas pode causar o inchaço do plano de consulta no servidor ao longo do tempo.
— Jim Wooley,

Você já tentou executar a mesma consulta duas vezes / várias vezes? Quanto tempo demorou ao executar pela segunda vez? Você já tentou fazer isso no .NET Framework 4.5 - existem algumas melhorias de desempenho relacionadas ao EF no .NET Framework 4.5 que podem ajudar.
— Pawel,

Respostas:


96

Encontrei. Acontece que é um problema de tipos de dados SQL. A SomeStringPropcoluna no banco de dados era um varchar, mas EF assume que os tipos de string .NET são nvarchars. O processo de tradução resultante durante a consulta para o banco de dados fazer a comparação é o que leva muito tempo. Acho que o EF Prof estava me enganando um pouco aqui, uma representação mais precisa da consulta que está sendo executada seria a seguinte:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Portanto, a correção resultante é anotar o modelo de código, indicando o tipo de dados SQL correto:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}

1
Boa investigação. Sua consulta estava sofrendo de "conversão implícita", conforme explicado aqui: brentozar.com/archive/2012/07/…
— Jaime

Me salvou algumas horas de depuração. Esse era exatamente o problema.
— Cody

1
No meu caso, estou usando o EDMX com um banco de dados legado, que usa varcharpara tudo, e de fato esse era o problema. Gostaria de saber se posso fazer um EDMX para considerar varchar para tudo coluna de string.
— Alisson

1
Ótima descoberta, homem. mas @Jaime o que devemos fazer para primeiro enfoque o banco de dados, pois tudo (por exemplo, anotação de dados em modelos db) desaparece após atualizar o modelo EF do banco de dados.
— Nauman Khan

Definindo isso como minha página inicial por um tempo para que eu possa reviver a emoção de encontrar uma resposta tão boa por um tempo. Obrigado!!!
— OJisBad

43

O motivo de desacelerar minhas consultas feitas no EF foi comparar escalares não anuláveis ​​com escalares anuláveis:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Essa consulta demorou 35 segundos. Mas uma pequena refatoração como essa:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

dá resultados incríveis. Demorou apenas 50 ms para ser concluído. É possível que seja um bug no EF.


13
Isso é tão estranho
— Daniel Cardenas

1
AMD. Aparentemente, isso também pode acontecer quando o uso de interfaces IUserId.Id estava causando o problema, mas primeiro o mapeamento de Id para um número inteiro funciona ... devo verificar agora todas as consultas em meu aplicativo de 100.000 linhas?
— Dirk Boer

este bug foi relatado? Ainda está na última versão 6.2.0
— Dirk Boer

2
O mesmo problema também está no EF Core. Obrigado por encontrar isso!
— Yannickv

Outra sugestão é processar a variável antes de colocá-la na expressão do LINQ. Caso contrário, o sql gerado será muito mais longo e lento. Eu experimentei ter Trim () e ToLower () dentro da expressão do LINQ que me incomoda.
— samheihey


4

Tive o mesmo problema (a consulta é rápida quando executada do gerenciador de SQL) mas quando executada do EF o tempo limite expira.

Acontece que a entidade (que foi criada a partir da visualização) tinha chaves de entidade erradas. Portanto, a entidade tinha linhas duplicadas com as mesmas chaves e acho que tinha que fazer o agrupamento no fundo.


3

Eu também me deparei com isso com uma consulta ef complexa. Uma correção para mim que reduziu uma consulta ef de 6 segundos para a consulta sql sub-segundo que gerou foi desligar o carregamento lento.

Para encontrar esta configuração (ef 6) vá para o arquivo .edmx e procure em Propriedades -> Geração de código -> Carregamento lento ativado. Defina como falso.

Melhoria maciça de desempenho para mim.


4
Isso é legal, mas não tem nada a ver com a questão dos pôsteres.
— Jace Rhea,

2

Eu tive esse problema também. Acontece que o culpado no meu caso foi a detecção de parâmetros do SQL Server .

A primeira pista de que meu problema era na verdade devido à detecção de parâmetro era que executar a consulta com "set arithabort off" ou "set arithabort on" gerava tempos de execução drasticamente diferentes no Management Studio. Isso ocorre porque o ADO.NET por padrão usa "set arithabort off" e o Management Studio usa como padrão "set arithabort on". O cache do plano de consulta mantém planos diferentes dependendo deste parâmetro.

Desativei o cache do plano de consulta para a consulta, com a solução que você encontra aqui .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.