У меня есть приложение MVC 4 с EF 6. После обновления с EF 5 до EF 6 я заметил проблему производительности с одним из моих запросов linq-объектов. Сначала я был взволнован, потому что на моем ящике разработки я заметил 50% -ное улучшение от EF 5 до EF 6. Этот запрос возвращает около 73 000 записей. SQL, выполняемый на производственном сервере, был перехвачен с помощью Activity Monitor, Recent Expensive Queries, это время также включено в следующие таблицы. Следующие числа после разогрева DB:
Разработка: 64-разрядная ОС, SS 2012, 2 ядра, 6 ГБ оперативной памяти, IIS Express.
EF 5 ~30 sec
EF 6 ~15 sec
SQL ~26 sec
Производство: 64-разрядная ОС, SS 2012, 32 ядра, 32 ГБ оперативной памяти, IIS8.
EF 5 ~8 sec
EF 6 ~4 minutes
SQL ~6 sec.
Я включил спецификации, чтобы дать представление о том, какая относительная производительность должна быть. Таким образом, кажется, что, когда я использую EF 6 в своей среде разработки, я получаю повышение производительности, когда публикую на своем производственном сервере огромную проблему с производительностью. Базы данных аналогичны, если не совсем то же самое. Все индексы были перестроены, SQL-запрос также, похоже, указывает на то, что нет причин подозревать, что база данных виновата. Пул приложений -.Net 4.0 в производстве. Оба сервера разработки и производства имеют .Net 4.5. Я не знаю, что проверить дальше или как отладить эту проблему, какие-нибудь идеи о том, что делать или как отлаживать дальше?
Update: Использование SQL Server Profiler обнаружило, что EF5 и EF6 создают несколько разные TSQL. Разница TSQL следующая:
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]
Этот же TSQL из EF6 также работает по-разному в зависимости от сервера/базы данных, на которой выполняется TSQL. После проверки плана запросов для EF6 и медленной базы данных (серийный сервер SS build 11.0.3000 Enterprise Edition) этот план выполняет все сканирование и не ищет по сравнению с идентичным экземпляром (тестовый сервер SS build 11.0.3128 Developers Edition), который имеет несколько запросов это делает разницу. Настенное время составляет > 4 минуты для производства и 12 секунд для небольшого тестового сервера. EF помещает эти запросы в sp_executesql proc, перехваченный sp_executesql proc использовался для указанного выше момента. Я не получаю медленное время (плохой план запроса) сгенерированным кодом EF5 или EF6 при выполнении на сервере разработки. Также странно, если я удалю TSQL из sp_executesql и запустил его на производственном сервере, запрос выполняется быстро (6 секунд). Итак, для медленного плана выполнения необходимо выполнить три вещи:
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.
Среда тестирования была создана с резервным копированием моих производственных данных, данные на обоих серверах идентичны. Может ли создание резервной копии и восстановление базы данных устранить некоторые проблемы с данными? Я не пытался удалить экземпляр и восстановить на рабочем сервере, потому что я хотел бы точно знать, в чем проблема, прежде чем удалять и восстанавливать экземпляр, на случай, если он устранит проблему. Я попытался сбросить кеш с помощью следующего TSQL
select DB_ID()
DBCC Flushprocindb(database_Id)
and
DBCC FREEPROCCACHE(plan_handle)
Промывка сверху не повлияла на план запроса. Любые предложения, что попробовать дальше?
Ниже приведен запрос 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();
SQL, который сгенерирован с этим запросом, кажется разумным. (Я не включил его, потому что, когда я вставляю его в него, он не отформатирован и не читается.)