ric*_*rdo 29 c# linq sql-server performance entity-framework
我有一个使用EF 6的MVC 4应用程序.从EF 5升级到EF 6后,我注意到我的一个linq-entities查询存在性能问题.起初我很兴奋,因为在我的开发盒中,我注意到从EF 5到EF 6的改进了50%.这个查询返回了大约73,000条记录.使用Activity Monitor,Recent Expensive Queries拦截在生产服务器上运行的SQL,此时间也包含在下表中.一旦DB被预热,以下数字:
开发:64位操作系统,SS 2012,2核,6 GB RAM,IIS Express.
EF 5 ~30 sec
EF 6 ~15 sec
SQL ~26 sec
Run Code Online (Sandbox Code Playgroud)
生产:64位操作系统,SS 2012,32核,32 GB RAM,IIS8.
EF 5 ~8 sec
EF 6 ~4 minutes
SQL ~6 sec.
Run Code Online (Sandbox Code Playgroud)
我已经包含了这些规范,只是为了了解相对性能应该是什么.因此,当我在我的开发环境中使用EF 6时,当我向生产服务器发布一个巨大的性能问题时,我得到了性能提升.如果不完全相同,数据库是相似的.所有索引都已重建,SQL查询似乎也表明没有理由怀疑数据库是否有问题.应用程序池是.Net 4.0正在生产中.开发和生产服务器都安装了.Net 4.5.我不知道下一步要检查什么,或者如何调试这个问题,关于做什么或如何进一步调试的任何想法?
更新: 使用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]
Run Code Online (Sandbox Code Playgroud)
来自EF6的相同TSQL也执行不同,具体取决于执行TSQL的服务器/数据库.在检查了EF6和慢速数据库(生产服务器SS build 11.0.3000企业版)的查询计划之后,与相同的实例(测试服务器SS build 11.0.3128开发人员版)相比,此计划执行所有扫描并且没有搜索这有所作为.生产时间为4分钟,小型测试服务器为12秒.EF将这些查询放入sp_executesql proc,截获的sp_executesql proc用于上述时序.在开发服务器上执行时,我不会使用EF5或EF6生成的代码获得慢时间(错误的查询计划).同样奇怪的是,如果我从sp_executesql中删除TSQL并在生产服务器上运行它,则查询会很快执行(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.
Run Code Online (Sandbox Code Playgroud)
测试环境是使用我的生产数据备份创建的,两台服务器上的数据完全相同.可以创建备份和恢复数据库修复了数据的一些问题吗?我没有尝试删除实例并在生产服务器上恢复,因为我想确定在删除和恢复实例之前问题是什么,以防万一它确实解决了问题.我尝试使用以下TSQL刷新缓存
select DB_ID()
DBCC Flushprocindb(database_Id)
and
DBCC FREEPROCCACHE(plan_handle)
Run Code Online (Sandbox Code Playgroud)
使用上面刷新不会影响查询计划.有什么建议接下来要尝试什么?
以下是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();
Run Code Online (Sandbox Code Playgroud)
使用此查询生成的SQL似乎是合理的.(我没有把它包括在内,因为当我把它粘贴在里面时它没有格式化并且难以阅读.)
在研究这个问题时,我发现了一些我不知道的关于SQL Server的东西.这对某些人来说可能是常识,但对我来说却不是.这是我的整体亮点.
linq改变的关键部分是:
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
Run Code Online (Sandbox Code Playgroud)
Pins表包含PinId的主键,而所有其他表都将PinId作为外键.将引脚保持为连接而不是导航属性可以提高性能.
| 归档时间: |
|
| 查看次数: |
6284 次 |
| 最近记录: |