Exécution de requêtes extrêmement lente et inefficace à partir d'Entity Framework

J'ai Entity Framework 4.1 avec .NET 4.5 fonctionnant sur ASP.NET dans Windows 2008R2. J'utilise le code EF-first pour me connecter à SQL Server 2008R2, et j'exécute une requête LINQ assez complexe, mais avec seulement un Count() .

J'ai reproduit le problème sur deux servers web différents mais une seule database (production bien sûr). Il a récemment commencé à se produire sans application, structure de database ou changement de server sur le web ou la database.

Mon problème est que l'exécution de la requête dans certaines circonstances prend un time ridicule (près de 4 minutes). Je peux prendre la requête réelle, tirée de SQL Profiler, et exécuter dans SSMS dans environ 1 seconde. Ceci est cohérent et reproductible pour moi, mais si je change la valeur de l'un des parameters (un paramètre "Date après 2015-01-22") à quelque chose plus tôt, comme 2015-01-01, ou plus tard, comme 2015-02- 01, ça fonctionne bien dans EF. Mais je le remets au 2015-01-22 et c'est encore lent. Je peux répéter cela encore et encore.

Je peux ensuite exécuter une requête similaire mais non liée dans EF, puis revenir à l'original, et cela fonctionne bien cette fois – même requête exacte que précédemment. Mais si j'ouvre un nouveau browser, le cycle recommence. Cette partie n'a aucun sens – nous ne faisons rien pour conserver le context de données dans une session d'user, ainsi je n'ai aucune idée de la raison pour laquelle cela entre en jeu.

Mais tout cela me dit que datatables elles-mêmes sont bien.

Dans Profiler, lorsque la requête s'exécute correctement, il faut environ une seconde ou deux, et montre environ 2 000 000 en lectures et environ 2 000 en CPU. Quand il s'exécute lentement, cela prend 3,5 minutes, et les valeurs sont 300 000 000 et 200 000 – les lectures sont donc environ 150 fois plus élevées et le processeur est 100 fois plus élevé. Encore une fois, pour l'instruction SQL identique.

Des suggestions sur ce que EF pourrait faire différemment ne s'afficheraient pas dans le text de la requête? Existe-t-il une sorte de propriété de connection cachée qui pourrait entraîner un plan d'exécution différent dans certaines circonstances?

MODIFIER

La requête que construit EF est l'une de celles où il construit une string géante avec le paramètre inclus dans le text, pas comme un paramètre SQL:

 exec sp_executesql N'SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT COUNT(1) AS [A1] ... AND ([Extent1].[Added_Time] >= convert(datetime2, ''2015-01-22 00:00:00.0000000'', 121)) ... ) AS [GroupBy1]' 

MODIFIER

Je n'ajoute pas cela comme une réponse, car cela ne résout pas vraiment le problème sous-jacent, mais cela a finalement été résolu en reconstruisant des index et en recalculant les statistics. Cela n'a pas été fait plus longtime que d'habitude, et il semble avoir éclairci tout ce qui a causé le problème.

Je vais continuer à lire certains des liens ici au cas où cela se reproduirait, mais comme tout fonctionne maintenant et n'est pas reproductible, je ne sais pas si je saurai exactement ce qu'il faisait.

Merci pour toutes les idees.

J'ai récemment eu un scénario très similaire, une requête s'exécuterait très rapidement en l'exécutant directement dans la database, mais aurait des performances terribles en utilisant EF (version 5, dans mon cas). Ce n'était pas un problème de réseau, la différence était de 4ms à 10 minutes.

Le problème a fini par être un problème de cartographie. J'avais une colonne mappée à NVARCHAR , alors que c'était VARCHAR dans la database. Cela semble inoffensif, mais cela a entraîné une conversion implicite dans la database, ce qui a complètement ruiné la performance.

Je ne suis pas tout à fait sûr de la raison pour laquelle cela se produit, mais à partir des tests que j'ai effectués, la database a fait un Index Index au lieu d'un Index Seek , et apparemment ils sont très différents en termes de performances.

analyse d'index de comparaison et recherche d'index

J'ai blogué à ce sujet ici (avertissement: il est en portugais), mais plus tard, j'ai trouvé que Jimmy Bogard a décrit ce problème exact dans un post de 2012, je vous suggère de vérifier.

Puisque vous avez une conversion dans votre requête, je dirais partir de là. Vérifiez à nouveau tous vos mappages de colonnes et vérifiez les différences entre la colonne de votre table et la propriété de votre entité. Évitez les conversions implicites dans votre requête.
Si vous le pouvez, vérifiez votre plan d'exécution pour find des incohérences, soyez conscient du sortingangle d'avertissement jaune qui peut indiquer des problèmes comme celui-ci à propos de la conversion implicite:

problème de requêteavertissement de conversion implicite

J'espère que cela vous aidera d'une façon ou d'une autre, c'était un problème très difficile à découvrir pour nous, mais qui a finalement eu un sens.

Il y a un excellent article sur la prise en count des performances d'Entity Framework ici .

Je voudrais attirer votre attention sur la section sur l'exécution de requête contre froid:

La toute première fois qu'une requête est effectuée sur un model donné, Entity Framework fait beaucoup de travail en coulisses pour charger et valider le model. Nous nous référons fréquemment à cette première requête comme une requête "froide". D'autres requêtes sur un model déjà chargé sont appelées requêtes "chaudes" et sont beaucoup plus rapides.

Lors de l'exécution de la requête LINQ, l'étape "Chargement des métadonnées" a un impact important sur les performances pour l'exécution de requêtes Cold. Cependant, une fois les métadonnées chargées seront mises en cache et les requêtes futures s'exécuteront beaucoup plus rapidement. Les métadonnées sont mises en cache à l'extérieur de DbContext et seront réutilisables tant que le pool d'applications sera en vie.

Pour améliorer les performances, tenez count des actions suivantes:

  • utiliser des vues pré-générées
  • utiliser la caching du plan de requête
  • n'utilise aucune requête de suivi (uniquement si vous accédez en lecture seule)
  • créer une image native de Entity Framework (uniquement utile si vous utilisez EF 6 ou une version ultérieure)

Tous ces points sont bien documentés dans le lien fourni ci-dessus. En outre, vous pouvez find des informations supplémentaires sur la création d'une image native de Entity Framework ici .

Je n'ai pas de réponse spécifique quant à la raison pour laquelle cela se produit, mais cela semble certainement lié à la façon dont la requête est traitée plus que la requête elle-même. Si vous dites que vous n'avez aucun problème à exécuter la même requête générée à partir de SSMS, alors ce n'est pas le problème.

Une solution de contournement que vous pouvez essayer: Une procédure stockée. EF peut très bien les gérer, et c'est le moyen idéal de gérer des requêtes potentiellement complexes ou coûteuses.

Juste pour mettre là dehors car il n'a pas été adressé comme une possibilité:

Étant donné que vous utilisez Entity Framework (EF), si vous utilisez le chargement différé d'entités, EF requirejs plusieurs sets de résultats actifs (MARS) à activer via la string de connection. Bien que cela puisse sembler totalement indépendant, MARS produit parfois ce comportement exact de quelque chose courant rapidement dans le SSMS mais horriblement lent (les secondes deviennent plusieurs minutes) via EF.

Une façon de tester ceci est de désactiver Lazy Loading et de supprimer MultipleActiveResultSets=True; (la valeur par défaut est "false") ou au less la changer pour être MultipleActiveResultSets=False; .

Pour autant que je sache, il n'y a malheureusement pas de solution de rechange ou de correction (actuellement) pour ce comportement.

Voici une instance de ce problème: La même requête avec le même plan de requête prend ~ 10 fois plus longtime lorsqu'elle est exécutée à partir d'ADO.NET par rapport à SMSS

Réalisant que vous utilisez Entity Framework 4.1, je vous suggère de passer à Entity Framework 6.

Il y a eu beaucoup d'amélioration des performances et EF 6 est beaucoup plus rapide que EF 4.1.

L' article MSDN sur la prise en count des performances des Entity Framework mentionné dans mon autre réponse a également une comparaison entre EF 4.1 et EF 6.

Il pourrait y avoir un peu de refactoring nécessaire, mais l'amélioration de la performance devrait en valoir la peine (et cela réduirait la dette technique en même time).