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.)