Pourquoi l'ajout d'un ToList () inutile accélère-t-il drastiquement cette requête LINQ? [fermé]

Pourquoi forcer la matérialisation en utilisant ToList() rend-elle mes ordres de grandeur plus rapides alors que, le cas échéant, cela devrait faire exactement le contraire?

1) appeler First() immédiatement

    // "Context" is an Entity Framework DB-first model

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.First();

    //  ** The above takes 30+ seconds to run **

2) Appelant First() après l'appel de ToList():

    var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

    var User = query.ToList().First();     // Added ToList() before First()

    // ** Now it takes < 1 second to run! **

Mise à jour et résolution

Après le SQL généré, la seule différence est, comme prévu, l'ajout de TOP (1) dans la première requête. Comme le ditAndyz Smith dans sa réponse ci-dessous, la racine la cause est que L'optimiseur SQL Server, dans ce cas particulier, choisit un plan d'exécution pire lorsque TOP (1) est ajouté. Ainsi, le problème n'a rien à voir avec LINQ (qui a fait la bonne chose en ajoutant TOP (1)) et tout à voir avec les idiosyncrasies de SQL Server.

21
demandé sur JoeCool 2013-08-20 18:04:03

3 réponses

Ainsi, l'optimiseur choisit une mauvaise façon d'exécuter la requête.

Puisque vous ne pouvez pas ajouter des conseils d'optimiseur au SQL pour forcer l'optimiseur à choisir un meilleur plan, je vois deux options.

  1. Ajouter un index de couverture / vue indexée sur toutes les colonnes qui sont récupérées / incluses dans le select assez ridicule, mais je pense que cela fonctionnera, car cet index rendra facile pour l'optimiseur de choisir un meilleur plan.

  2. Toujours se matérialiser prématurément requêtes qui incluent First ou Last ou Take.  Dangereux car à mesure que les données grossissent, le point d'équilibre entre tirer toutes les données localement et faire le premier () et faire la requête avec Top sur le serveur va changement.

Http://geekswithblogs.net/Martinez/archive/2013/01/30/why-sql-top-may-slow-down-your-query-and-how.aspx

Https://groups.google.com/forum/m/#!topic/microsoft.public.sqlserver.server/L2USxkyV1uw

Http://connect.microsoft.com/SQLServer/feedback/details/781990/top-1-is-not-considered-as-a-factor-for-query-optimization

TOP ralentit la requête

Pourquoi TOP ou SET ROWCOUNT fait-il ma requête si lente?

0
répondu Andyz Smith 2017-05-23 11:44:17

Je ne peux penser qu'à une seule raison... Pour le tester, pouvez-vous supprimer la clause Where et relancer le test? Commentaire ici si le résultat est la première déclaration étant plus rapide, et je vais vous expliquer pourquoi.

Modifier
Dans la clause Linq statement Where, vous utilisez le .Méthode ToLower() de la chaîne. Je suppose que LINQ n'a pas de conversion intégrée en SQL pour cette méthode, donc le SQL résultant est quelque chose line

SELECT *
FROM Users

Maintenant, nous savons que LINQ charges paresseuses, mais il sait également que puisqu'il n'a pas évalué la clause WHERE, il doit charger les éléments pour faire la comparaison.

Hypothèse
La première requête est le chargement paresseux chaque élément dans le jeu de résultats. Il est ensuite de faire de la .Comparaison ToLower () et retour du premier résultat. Il en résulte des requêtes n au serveur et une surcharge de performances énorme. Impossible d'être sûr sans voir le Tracelog SQL.

La deuxième instruction appelle ToList, qui demande un batch SQL avant de faire la comparaison ToLower, résultant en une seule requête au serveur

Hypothèse Alternative
Si le profileur n'affiche qu'une seule exécution de serveur, essayez d'exécuter la même requête avec la clause Top 1 et voyez si cela prend autant de temps. Selon ce post (Pourquoi faire un top (1)sur une colonne indexée dans SQL Server est-il lent?) la clause TOP peut parfois jouer avec L'optimiseur SQL server et l'arrêter en utilisant les indices corrects.

La Curiosité modifier
essayez de changer le LINQ en

var query = from x in Context.Users
            where x.Username.Equals(User.Identity.Name, StringComparison.OrdinalIgnoreCase)
            select x;

Crédit à @ Scott pour avoir trouvé le moyen de faire une comparaison insensible à la casse dans LINQ. Donnez-lui un aller et voir si elle est plus rapide.

11
répondu David Colwell 2017-05-23 12:32:42

SQL ne sera pas le même que Linq est chargement paresseux. Ainsi, votre appel à .ToList() forcera. net à évaluer l'expression, puis en mémoire, sélectionnez l'élément first().

Où comme l'autre option devrait ajouter top 1 dans le SQL

E. G.

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here
 var User = query.First();

Et

var query = from x in Context.Users
                where x.Username.ToLower().Equals(User.Identity.Name.ToLower())
                select x;

 //SQL executed here!
 var list = query.ToList();
 var User = query.First();

Comme ci-dessous, la première requête devrait être plus rapide! Je suggère de faire un SQL profiler pour voir ce qui se passe. La vitesse des requêtes dépendra de votre structure de données, le nombre de enregistrements, index, etc.

Le moment de votre test modifiera également les résultats. Comme quelques personnes l'ont mentionné dans les commentaires, la première fois que vous appuyez sur EF, il doit initialiser et charger les métadonnées. donc, si vous exécutez ces ensemble, le premier devrait toujours être lent.

Voici quelques informations supplémentaires sur EF considérations sur les performances

Notez la ligne:

Les métadonnées du modèle et du mappage utilisées par Entity Framework sont chargées dans un espace MetadataWorkspace. Ces métadonnées sont mises en cache globalement et sont disponibles à d'autres instances de ObjectContext dans le même domaine d'application.

&

Parce qu'une connexion ouverte à la base de données ressource, le cadre D'entité ouvre et ferme la base de données connexion uniquement au besoin. Vous pouvez également ouvrir explicitement le connexion. Pour plus d'informations, voir Gestion des connexions et Transactions dans le cadre de L'entité.

3
répondu Liam 2017-05-23 11:52:52