27 votes

EF 6 vs EF 5 relative problème de performance lors du déploiement de IIS8

J'ai un MVC 4 application avec EF 6. Après la mise à niveau de EF 5 à EF 6, j'ai remarqué un problème de performance avec l'un de mes linq-requêtes d'entités. Au début, j'étais excité parce que sur mon développement de la boîte, j'ai remarqué une amélioration de 50% de EF 5 à EF 6. Cette requête renvoie environ 73 000 dossiers. Le SQL en cours d'exécution sur le serveur de production a été intercepté avec le Moniteur d'Activité, les Dernières Requêtes Cher, ce moment est également inclus dans les tableaux suivants. Les numéros suivants sont une fois DB est réchauffé:

Développement: 64 bits de l'OS, SS 2012, 2 carottes, 6 GO de RAM, IIS Express.

EF 5 ~30 sec
EF 6 ~15 sec
SQL ~26 sec

Production: 64 bits de l'OS, SS 2012, 32 cœurs, 32 GO de RAM, IIS8.

EF 5 ~8 sec
EF 6 ~4 minutes
SQL ~6 sec.

J'ai inclus les spécifications juste pour donner une idée de ce que la performance relative devrait être. Il semble donc que, lorsque j'utilise EF 6 dans mon environnement de développement-je obtenir de l'amélioration de la performance, quand j'ai publier sur mon serveur de production un énorme problème de performance. Les bases de données sont similaires, si ce n'est pas exactement la même. Tous les indices ont été reconstruits, la requête SQL semble également indiquer qu'il n'y a aucune raison de soupçonner une base de données est en faute. Pool d'applications est .Net 4.0 dans la production. À la fois le développement et la production de serveur .Net 4.5 est installé. Je ne sais pas quoi vérifier suivant ou comment déboguer ce problème, des idées sur quoi faire ou comment déboguer plus loin?

Mise à jour: À l'aide de SQL Server Profiler constaté que EF5 et EF6 produire légèrement différente TSQL. Le TSQL différence est comme suit:

EF5: LEFT OUTER JOIN [dbo].[Pins] AS [Extent9] ON [Extent1].[PinId] = [Extent9].[PinID]
EF6: INNER JOIN [dbo].[Pins] AS [Extent9] ON [Extent1].[PinId] = [Extent9].[PinID]

Cette même TSQL de EF6 effectue également différemment selon le serveur de base de données sur le TSQL est exécutée. Après l'inspection de plan de requête pour EF6 & lent de base de données (serveur de production SS construire 11.0.3000 Enterprise Edition) ce plan n'tous les scans et ne cherche comparativement à l'identique de l'instance (serveur de test SS construire 11.0.3128 Développeurs Édition) qui a un peu cherche qui font la différence. Horloge murale temps est > 4 min pour la production et 12 sec pour petit serveur de test. EF lieux de ces requêtes dans sp_executesql proc, le intercepté sp_executesql proc a été utilisée pour le calendrier mentionné ci-dessus. Je n'ai PAS ralentir le temps (mauvais plan de requête) avec EF5 ou EF6 code généré lors de l'exécution sur le serveur de développement. Aussi étrange, si je supprime TSQL de sp_executesql et de l'exécuter sur le serveur de production de la requête est exécutée rapidement (6 sec). En résumé, trois choses doivent se produire pour ralentir l'exécution du plan:

1. Execute on production server build 11.0.3000
2. Use Inner Join with Pins table (EF6 generated code).
3. Execute TSQL inside of sp_executesql.

L'environnement de test a été créé avec une sauvegarde de mes données de production, les données sur les deux serveurs est identique. Pourrait créer une copie de sauvegarde et restauration de la base de données ont fixé un problème avec les données? Je n'ai pas essayé de la suppression de l'instance et de la restauration sur le serveur de production, parce que je voudrais savoir pour vous quel est le problème avant de me supprimer et restaurer l'instance, juste au cas où il n'résoudre le problème. Je l'ai fait essayer et de vider le cache avec la TSQL suivante

select DB_ID() 
DBCC Flushprocindb(database_Id)
and 
DBCC FREEPROCCACHE(plan_handle)

Rincer avec de ci-dessus n'a pas d'effet sur le plan de requête. Toutes les suggestions de ce à essayer ensuite?

Voici la requête linq:

    result =
    (
    from p1 in context.CookSales

    join l2 in context.CookSaleStatus on new { ID = p1.PinId, YEAR = year1 } equals new { ID = l2.PinId, YEAR = l2.StatusYear } into list2
    from p3 in list2.DefaultIfEmpty()
    join l3 in context.CookSaleStatus on new { ID = p1.PinId, YEAR = year2 } equals new { ID = l3.PinId, YEAR = l3.StatusYear } into list3
    from p4 in list3.DefaultIfEmpty()
    join l4 in context.CookSaleStatus on new { ID = p1.PinId, YEAR = year3 } equals new { ID = l4.PinId, YEAR = l4.StatusYear } into list4
    from p5 in list4.DefaultIfEmpty()
    join l10 in context.CookSaleStatus on new { ID = p1.PinId, YEAR = year4 } equals new { ID = l10.PinId, YEAR = l10.StatusYear } into list10
    from p11 in list10.DefaultIfEmpty()

    join l5 in context.ILCookAssessors on p1.PinId equals l5.PinID into list5
    from p6 in list5.DefaultIfEmpty()
    join l7 in context.ILCookPropertyTaxes on new { ID = p1.PinId } equals new { ID = l7.PinID } into list7
    from p8 in list7.DefaultIfEmpty()

    join l13 in context.WatchLists on p1.PinId equals l13.PinId into list13
    from p14 in list13.DefaultIfEmpty()

    join l14 in context.Pins on p1.PinId equals l14.PinID into list14
    from p15 in list14.DefaultIfEmpty()
    orderby p1.Volume, p1.PIN
    where p1.SaleYear == userSettings.SaleYear 
    where ((p1.PinId == pinId) || (pinId == null))
    select new SaleView
    {
        id = p1.id,
        PinId = p1.PinId,
        Paid = p1.Paid == "P" ? "Paid" : p1.Paid,
        Volume = p1.Volume,
        PinText = p15.PinText,
        PinTextF = p15.PinTextF,
        ImageFile = p15.FnaImage.TaxBodyImageFile,
        SaleYear = p1.SaleYear,
        YearForSale = p1.YearForSale,
        Unpaid = p1.DelinquentAmount,
        Taxes = p1.TotalTaxAmount,
        TroubleTicket = p1.TroubleTicket,
        Tag1 = p1.Tag1,
        Tag2 = p1.Tag2,
        HasBuildingPermit = p1.Pin1.BuildingPermitGeos.Any(p => p.PinId == p1.PinId),
        BidRate = p1.BidRate,
        WinningBid = p1.WinningBid,
        WinningBidderNumber = p1.BidderNumber,
        WinningBidderName = p1.BidderName,
        TaxpayerName = p1.TaxpayerName,
        PropertyAddress = SqlFunctions.StringConvert((double?)p1.TaxpayerPropertyHouse) + " " + p1.TaxpayerPropertyDirection + " "
                        + p1.TaxpayerPropertyStreet
                        + " " + p1.TaxpayerPropertySuffix +
                        System.Environment.NewLine + (p1.TaxpayerPropertyCity ?? "") + ", " + (p1.TaxpayerPropertyState ?? "") +
                        " " + (p1.TaxpayerPropertyZip ?? ""),
        MailingAddress = (p1.TaxpayerName ?? "") + System.Environment.NewLine + (p1.TaxpayerMailingAddress ?? "") +
                        System.Environment.NewLine + (p1.TaxpayerMailingCity ?? "") + ", " + (p1.TaxpayerMailingState ?? "") +
                        " " + (p1.TaxpayerMailingZip ?? ""),
        Status1 = p3.Status.Equals("Clear") ? null : p3.Status,
        Status2 = p4.Status.Equals("Clear") ? null : p4.Status,
        Status3 = p5.Status.Equals("Clear") ? null : p5.Status,
        Status4 = p11.Status.Equals("Clear") ? null : p11.Status,
        Township = p6.Township,
        AssessorLastUpdate = p6.LastUpdate,
        Age = p6.Age,
        LandSquareFootage = p6.LandSquareFootage,
        BuildingSquareFootage = p6.BuildingSquareFootage,
        CurrLand = p6.CurrLand,
        CurrBldg = p6.CurrBldg,
        CurrTotal = p6.CurrTotal,
        PriorLand = p6.PriorLand,
        PriorBldg = p6.PriorBldg,
        PriorTotal = p6.PriorTotal,
        ClassDescription = p6.ClassDescription,
        Class = p1.Classification == null ? p6.Class.Trim() : p1.Classification,
        TaxCode = p6.TaxCode,
        Usage = p6.Usage,

        Status0 = (p8.CurrentTaxYear != null && p8.CurrentTaxYearPaidAmount == 0) ? "Paid" : null, 
        LastTaxYearPaidAmount = p8.LastTaxYearPaidAmount,
        NoteStatus = p15.PinNotes.Any(p => p.PinId == p15.PinID),
        EntryComment = p1.EntryComment,
        IsInScavenger = p14.IsInScavenger ?? false,
        IsInTbs = p14.IsInTbs ?? false,
        RedeemVts = (p3.Redeemed == "VTS" || p4.Redeemed == "VTS" || p5.Redeemed == "VTS" || p11.Redeemed == "VTS") ? true : false,
        FivePercenter = (p3.FivePercenter || p4.FivePercenter || p5.FivePercenter || p11.FivePercenter) ? true : false,
    }
    ).ToList();

La requête SQL générée avec cette requête semble raisonnable. (Je n'ai pas inclus, car quand je le coller dans il n'est pas formaté et difficile à lire.)

8voto

ricardo Points 305

Alors que des recherches sur ce problème j'ai déniché plusieurs choses à propos de SQL Server que je ne connaissais pas. Cela peut être commun de connaissances pour certains, mais pour moi, ce n'était pas le cas. Voici mon ensemble des faits saillants.

  1. EF utilise le sql dynamique pour toutes les requêtes spécifiquement sp_exectutesql(). sp_executesql() exécute SQL dynamique, si vous supprimez cette SQL et exécuter en tant que adhoc requête dans SSMS ne vous attendez pas à obtenir les mêmes résultats. C'est bien documentés ici et les références de ce document que je vous recommande vivement de lire si vous avez ces types de problèmes.
  2. EF5 produit différents SQL dynamique que EF6 sous certaines conditions.
  3. Il est difficile d'optimiser linq to entities parce que vous pouvez obtenir des résultats différents selon le matériel, c'est expliqué dans les références. Mon but initial était d'optimiser la requête linq lorsque j'ai mis à EF6. J'ai remarqué que pas en utilisant les propriétés de navigation amélioré les performances de mon dev et les serveurs de test, mais il l'a tuée dans la production.
  4. Résultat Final avec des performances acceptables, dans tous les environnements, était une combinaison de rejoindre et les propriétés de navigation. En fin de compte Si j'avais utilisé toutes les propriétés de navigation, il aurait donné de meilleurs résultats depuis le début. La jointure clés utilisés étaient de la mauvaise tables, lorsque vous écrivez SQL ad hoc, il n'a pas d'importance, mais il doit pour SQL dynamique. Avais-je utilisé la navigation, il n'y aurait eu toutes les clés pour se tromper. Cependant, la meilleure performance a été avec une jointure et les autres propriétés de navigation. La dynamique SQL généré est remarquablement similaire pour tous les scénarios, mais la requête SQL Server plan de l'optimiseur est de mieux en mieux les indices lors de la navigation propriétés sont utilisées (c'est une supposition).

La partie clé de linq changé, c'est ceci:

                from p1 in context.CookSales
                join p15 in context.Pins on p1.PinId equals p15.PinID
                where p1.SaleYear == userSettings.SaleYear
                where ((p1.PinId == pinId) || (pinId == null))
                orderby p1.Volume, p1.PIN
                select new SaleView bla bla

Les Broches de la table qui contient la clé primaire pour PinId alors que toutes les autres tables ont PinId comme clé étrangère. Garder les Broches que la jointure et pas une propriété de navigation amélioration de la performance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X