使用entity framework的SQL查询运行速度较慢,使用错误的查询计划

我一般成功使用entity framework,但一个查询运行速度非常慢。 查询(由EF生成)如下:

exec sp_executesql N'SELECT [Project1].[downtimeId] AS [downtimeId], CASE WHEN ([Extent12].[downtimeStart] > @p__linq__7) THEN [Extent13].[downtimeStart] ELSE @p__linq__8 END AS [C1], CASE WHEN ([Extent14].[equipmentID] IS NULL) THEN 0 ELSE [Extent15].[equipmentID] END AS [C2], CASE WHEN ([Extent16].[equipmentID] IS NULL) THEN N''Unit Overhead'' ELSE [Extent18].[equipmentCode] END AS [C3], CASE WHEN ( CAST( [Project1].[downtimeEquipmentStart] AS datetime2) > @p__linq__9) THEN CAST( [Project1].[downtimeEquipmentStart] AS datetime2) ELSE @p__linq__10 END AS [C4], CASE WHEN ( CAST( [Project1].[downtimeEquipmentEnd] AS datetime2) = @p__linq__1) AND ([Extent6].[downtimeStart] < @p__linq__2) AND ([Project1].[downtimeEquipmentStart]  @p__linq__4) AND ((([Extent7].[processUnitID] = @p__linq__5) AND ( NOT ([Extent8].[processUnitID] IS NULL OR @p__linq__5 IS NULL))) OR (([Extent9].[processUnitID] IS NULL) AND (@p__linq__5 IS NULL))) AND (@p__linq__6 = 1 OR [Extent11].[includeInDowntimeAnalysis] = 1)',N'@p__linq__0 int,@p__linq__1 datetime2(7),@p__linq__2 datetime2(7),@p__linq__3 datetime2(7),@p__linq__4 datetime2(7),@p__linq__5 int,@p__linq__6 bit,@p__linq__7 datetime2(7),@p__linq__8 datetime2(7),@p__linq__9 datetime2(7),@p__linq__10 datetime2(7),@p__linq__11 datetime2(7),@p__linq__12 datetime2(7)',@p__linq__0=1,@p__linq__1='2015-03-02 00:00:00',@p__linq__2='2015-05-09 00:00:00',@p__linq__3='2015-05-09 00:00:00',@p__linq__4='2015-03-02 00:00:00',@p__linq__5=1,@p__linq__6=1,@p__linq__7='2015-03-02 00:00:00',@p__linq__8='2015-03-02 00:00:00',@p__linq__9='2015-03-02 00:00:00',@p__linq__10='2015-03-02 00:00:00',@p__linq__11='2015-05-09 00:00:00',@p__linq__12='2015-05-09 00:00:00' 

这是使用SQL Server Profiler捕获的。 当我使用SSMS查询窗口运行时,我在2秒内获得8000+行。 使用set statistics io ,我发现它可以执行大约16,000个逻辑读取。

当我使用EF查看来自我的网站的相同查询时,它在30秒后超时,完成了超过140万次逻辑读取。

使用分析器,我看到两个会话在登录期间都有以下设置:

 -- network protocol: TCP/IP set quoted_identifier on set arithabort off set numeric_roundabort off set ansi_warnings on set ansi_padding on set ansi_nulls on set concat_null_yields_null on set cursor_close_on_commit off set implicit_transactions off set language us_english set dateformat mdy set datefirst 7 set transaction isolation level read committed 

两个查询都使用相同的登录名和密码完成。

我看到SSMS正在向服务器发送一些set ,所以我在查询之前将以下代码添加到我的C#EF应用程序中:

 _context.Database.ExecuteSqlCommand( "SET ROWCOUNT 0 SET TEXTSIZE 2147483647 SET NOCOUNT OFF SET CONCAT_NULL_YIELDS_NULL ON SET ARITHABORT ON SET LOCK_TIMEOUT -1 SET QUERY_GOVERNOR_COST_LIMIT 0 SET DEADLOCK_PRIORITY NORMAL SET TRANSACTION ISOLATION LEVEL READ COMMITTED SET ANSI_NULLS ON SET ANSI_NULL_DFLT_ON ON SET ANSI_PADDING ON SET ANSI_WARNINGS ON SET CURSOR_CLOSE_ON_COMMIT OFF SET IMPLICIT_TRANSACTIONS OFF SET QUOTED_IDENTIFIER ON"); 

(为了便于阅读,增加了回车)

我假设在查询结尾处作为参数传递的日期被不同地解释,在来自EF时强制进行隐式数据类型转换,但我不知道如何处理它。[注意稍后编辑: 这是不正确的,而不是问题的根源 ]

请不要告诉我将其作为存储过程。

C#代码如下:

  _context.Database.ExecuteSqlCommand( "SET ROWCOUNT 0 SET TEXTSIZE 2147483647 SET NOCOUNT OFF SET CONCAT_NULL_YIELDS_NULL ON SET ARITHABORT ON SET LOCK_TIMEOUT -1 SET QUERY_GOVERNOR_COST_LIMIT 0 SET DEADLOCK_PRIORITY NORMAL SET TRANSACTION ISOLATION LEVEL READ COMMITTED SET ANSI_NULLS ON SET ANSI_NULL_DFLT_ON ON SET ANSI_PADDING ON SET ANSI_WARNINGS ON SET CURSOR_CLOSE_ON_COMMIT OFF SET IMPLICIT_TRANSACTIONS OFF SET QUOTED_IDENTIFIER ON"); DateTime sdate = startDate; var downtimeQueryRaw = from de in _context.DowntimeEquipments join p in sequenceQuery on de.Downtime.equipmentID equals p.equipmentID into sequenceEquipments from sequence in sequenceEquipments.DefaultIfEmpty() where de.Downtime.downtimeEnd >= sdate && de.Downtime.downtimeStart < workingEnd && de.downtimeEquipmentStart  sdate && de.Downtime.processUnitID == processUnitId && (includeUncontrollable || de.Downtime.DownTimeType.includeInDowntimeAnalysis) select new DowntimeCostByEquipmentRaw { DowntimeStart = ((de.Downtime.downtimeStart>sdate) ? de.Downtime.downtimeStart : sdate), EquipmentId = de.Downtime.equipmentID ?? 0, EquipmentCode = (de.Downtime.equipmentID == null ? "Unit Overhead" : de.Downtime.Equipment.equipmentCode), Start = ((((DateTime)de.downtimeEquipmentStart)>sdate) ?((DateTime)de.downtimeEquipmentStart) : sdate), End = ((((DateTime)de.downtimeEquipmentEnd) < workingEnd) ?((DateTime)de.downtimeEquipmentEnd) : workingEnd), StandardHourRate = de.Equipment.standardHourRate ?? 0, ReportingSequence = (de.Downtime.equipmentID == null ? 0 : sequence.reportingSequence) }; var downtimeList = downtimeQueryRaw.ToList(); 

问题是我的查询的陈旧或不正确的查询计划。

我解决了删除此查询的现有查询计划的问题。

感谢Vladimir Baranov将我指向sommarskog.se/query-plan-mysteries.html。 还要感谢tschmit007和annemartijn。

我必须使用以下查询在数据库中确定查询的查询计划:

 SELECT qs.plan_handle, a.attrlist, est.dbid, text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) est CROSS APPLY (SELECT epa.attribute + '=' + convert(nvarchar(127), epa.value) + ' ' FROM sys.dm_exec_plan_attributes(qs.plan_handle) epa WHERE epa.is_cache_key = 1 ORDER BY epa.attribute FOR XML PATH('')) AS a(attrlist) WHERE est.text LIKE '%standardHourRate%' and est.text like '%q__7%'and est.text like '%Unit Overhead%' AND est.text NOT LIKE '%sys.dm_exec_plan_attributes%' 

这是来自sommarskog论文的查询的轻微修改版本。 请注意,您必须将自己的代码放在like语句中以查找查询。 此查询使用我的查询的每个查询计划的属性列表和计划句柄进行响应。

我试图弄清楚哪个计划来自SSMS,哪个计划来自EF,所以我删除了所有这些计划,使用以下语法:

 dbcc freeproccache([your plan handle here]) 

为我的EF查询创建的新计划完美无缺。 显然,EF计划没有考虑到我最近更新了数据库的统计数据。 不幸的是,我不知道如何为EF查询执行sp_recompile。