实体框架缓存的查询计划性能随着不同的参数而降级 [英] Entity Framework cached query plan performance degrades with different parameters
问题描述
我有以下问题。
背景
我试图实现一个自动完成选择器MVC3,EF4和jquery表上有450万条记录。
这是表:
CREATE TABLE [dbo]。[CONSTA](
[afpCUIT] nvarchar(11)COLLATE Modern_Spanish_CI_AS NOT NULL,
[afpNombre] nvarchar(30)COLLATE Modern_Spanish_CI_AS NULL,
[afpGanancias] varchar(2)COLLATE Modern_Spanish_CI_AS NULL,
[afpIVA] varchar(2)COLLATE Modern_Spanish_CI_AS NULL,
[afpMonot] varchar(2)COLLATE Modern_Spanish_CI_AS NULL,
[afpIntSoc] varchar(1)COLLATE Modern_Spanish_CI_AS NULL,
[afpEmpl] varchar(1)COLLATE Modern_Spanish_CI_AS NULL,
[afpAct] varchar(2)COLLATE Modern_Spanish_CI_AS NULL,
CONSTRAINT [CONSTA_pk] PRIMARY KEY CLUSTERED [afpCUIT])
)
ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [CONSTA_Nombre_idx] ON [dbo]。[CONSTA]
([ afpNombr e])
WITH(
PAD_INDEX = OFF,
DROP_EXISTING = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = OFF,
ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
GO
该表是非常静态的(它只需要每月批量更新)和只读。
如果有人关心下载记录(54MB)这是URL:
http://www.afip.gob.ar/genericos/cInscripcion/22102011.zip
这里是记录描述:
http://www.afip。 gob.ar/genericos/cInscripcion/archivoCompleto.asp
这是应用程序的代码:
控制器:
public class AltaMasivaController:Controller
{
//
// GET:/ AltaMasiva /
public ActionResult Index()
{
return View();
}
public JsonResult GetUsers(string query)
{
CENT2Entities db = new CENT2Entities();
bool isCUIT = true; (int j = 0; j< query.Length; j ++)
if(!Char.IsDigit(query,j))
{
isCUIT = false;
break;
}
if(isCUIT)
{
// nvarchar search
var x = from db in db.CONSTA
其中u。 afpCUIT.StartsWith(query)
orderby u.afpNombre
select new {label = u.afpNombre.TrimEnd(),id = u.afpCUIT};
return Json(x.Take(50),JsonRequestBehavior.AllowGet);
}
else
{
// nvarchar搜索
var x =从db中的u
其中u.afpNombre.StartsWith(query)
orderby u.afpNombre
select new {label = u.afpNombre.TrimEnd(),id = u.afpCUIT};
return Json(x.Take(50),JsonRequestBehavior.AllowGet);
}
}
}
查看:
@ {
viewbag.title =index;
}
< h2> index< / h2>
@ html.textbox(user,,new {style =width:400px;})
< script type =text / javascript>
$(input#user)。autocomplete(
{
source:function(request,response)
{
//定义一个函数调用你的动作(假设usercontroller)
$ .ajax(
{
url:'/ altamasiva / getusers',键入:post,datatype:json,
//查询将是您的操作方法使用的参数
data:{query:request.term},
success:function(data){
response ($ .map(data,function(item){return {label:item.label +(+ item.id +),value:item.label,id:item.id};}));
}
})
},
minlength:1,//从用户至少需要一个字符
});
< / script>
现在:
问题
如您所见,代码遵循不同的路径,如果查询字符串只包含数字。
当所有控制器参数的字符是数字(其中u.afpCUIT.StartsWith(query)),查询优化器应该执行聚簇索引搜索(它所做的),并返回它找到的前50行。
当第一个自动完成字符串到达(通常最多一个或两个字符)时,查询执行速度非常快,但是当字符串的长度增加时,性能显着降低(几乎在20秒到2分钟与9个或更多的字符)。
令人惊讶的是,在重新启动SQL Server服务之后,如果初始字符串包含10个字符,那么它表现也很好,但是当我们从查询字符串中删除字符串时,性能就会降低,完全相反。 >
为什么会发生这种情况?
当SQL Server编译第一个执行计划时,它会优化其执行速度,大结果集(或反之亦然)。后续查询缩小(或扩展)结果集,需要不同的执行计划...但是...生成的SQL使用commad参数(精确地)避免语句重新编译...
执行执行计划缓存:执行:
db.ExecuteStoreCommand(DBCC FREEPROCCACHE);
将性能恢复到出色的响应时间...但它会杀死所有数据库中的计划,从而降低所有其他缓存计划的性能(通常可以执行)。
在对EF sql语句进行一些分析之后,我在查询分析器中执行了DBCC FREEPROCCACHE到sql EF生成,其原理是生成不同的执行计划,所有执行计划都在250ms范围内执行,与参数长度无关:
DBCC FREEPROCCACHE
exec sp_executesql N'SELECT TOP(50)
[Project1]。[C1] AS [C1],
[Project1]。[C2] AS [ C2],
[Project1]。[afpCUIT] AS [afpCUIT]
FROM(SELECT
[Extent1]。[afpCUIT] AS [afpCUIT],
[Extent1] afpNombre] AS [afpNombre],
1 AS [C1],
RTRIM([Extent1]。[afpNombre])AS [C2]
FROM [dbo]。[CONSTA] AS [Extent1 ]
WHERE [Extent1]。[afpCUIT] LIKE @ p__linq__0 ESCAPE N''〜''
)AS [Project1]
ORDER BY [Project1]。[afpNombre] ASC',N'@ p__linq__0 nvarchar(4000)',@ p__linq__0 = N'2023291%'
问题
有没有更可贵的替代方案
db.ExecuteStoreCommand(DBCC FREEPROCCACHE);
?
令人惊讶的是,的查询(其中u.afpNombre.StartsWith(query))不受相同问题的影响,并且表现很好。显然,当字符串的长度改变时,执行计划不会改变...
我在旧版本的EF中找到了一个ObjectContext参数:
System.Data.EntityClient.EntityCommand.EnablePlanCaching
但我在EF4找不到,我不知道全局结果是否一样。
我真的很困惑有这个问题,我不知道真正的问题在哪里
差的指数设计?
缺少分区?
SQL SERVER 2008 Express版?
EF生成SQL?
运气好吗?
任何帮助都会很棒。
Thanx提前!
有一种方法可以从SQL Server的缓存中删除单个计划。
这里详细解释:
http://sqlblog.com/blogs/kalen_delaney/archive/2007/09/29/geek-city-clearing-a-single-plan-from-cache.aspx
此外,您可以创建一个存储过程,并将其与实体框架进行映射,而不是使用LINQ2Entities,从而对SQL语法进行更改,并确保它总是一样。
I have the following problem.
Background
I'm trying to implement an autocomplete selector with MVC3, EF4 and jquery over a table wit 4.5 million records.
This is the table:
CREATE TABLE [dbo].[CONSTA] (
[afpCUIT] nvarchar(11) COLLATE Modern_Spanish_CI_AS NOT NULL,
[afpNombre] nvarchar(30) COLLATE Modern_Spanish_CI_AS NULL,
[afpGanancias] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
[afpIVA] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
[afpMonot] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
[afpIntSoc] varchar(1) COLLATE Modern_Spanish_CI_AS NULL,
[afpEmpl] varchar(1) COLLATE Modern_Spanish_CI_AS NULL,
[afpAct] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
CONSTRAINT [CONSTA_pk] PRIMARY KEY CLUSTERED ([afpCUIT])
)
ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [CONSTA_Nombre_idx] ON [dbo].[CONSTA]
([afpNombre])
WITH (
PAD_INDEX = OFF,
DROP_EXISTING = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = OFF,
ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
GO
The table is pretty static (it only needs a monthly batch update) and read only.
if somebody cares to download the records (54MB) this is the URL:
http://www.afip.gob.ar/genericos/cInscripcion/22102011.zip
and here is the record description:
http://www.afip.gob.ar/genericos/cInscripcion/archivoCompleto.asp
Here is the code of the app:
CONTROLLER:
public class AltaMasivaController : Controller
{
//
// GET: /AltaMasiva/
public ActionResult Index()
{
return View();
}
public JsonResult GetUsers(string query)
{
CENT2Entities db = new CENT2Entities();
bool isCUIT = true;
for(int j = 0; j < query.Length; j++)
if (! Char.IsDigit(query, j))
{
isCUIT = false;
break;
}
if (isCUIT)
{
// nvarchar search
var x = from u in db.CONSTA
where u.afpCUIT.StartsWith(query)
orderby u.afpNombre
select new { label = u.afpNombre.TrimEnd(), id = u.afpCUIT };
return Json(x.Take(50), JsonRequestBehavior.AllowGet);
}
else
{
// nvarchar search
var x = from u in db.CONSTA
where u.afpNombre.StartsWith(query)
orderby u.afpNombre
select new { label = u.afpNombre.TrimEnd(), id = u.afpCUIT };
return Json(x.Take(50), JsonRequestBehavior.AllowGet);
}
}
}
VIEW:
@{
viewbag.title = "index";
}
<h2>index</h2>
@html.textbox("user", "", new { style="width: 400px;" })
<script type="text/javascript">
$("input#user").autocomplete(
{
source: function (request, response)
{
// define a function to call your action (assuming usercontroller)
$.ajax(
{
url: '/altamasiva/getusers', type: "post", datatype: "json",
// query will be the param used by your action method
data: { query: request.term },
success: function(data){
response( $.map(data, function (item){ return { label: item.label + " (" + item.id + ")", value: item.label, id: item.id }; }));
}
})
},
minlength: 1, // require at least one character from the user
});
</script>
And now:
THE PROBLEM
As you can see, The code follows different paths if the query string contains only numbers.
When all the characters of the controller parameter are numbers (where u.afpCUIT.StartsWith(query) ), the query optimizer "is supposed to" perform a clustered index seek (which it does) and return the first 50 rows it finds. When the first "autocomplete" string arrives (usually one or two characters at most) the query performs extraordinarily fast, but, when the lenght of the string increases, the performance degrades notably (it takes almost between 20 seconds to 2 minutes with 9 or more chars). Surprisingly, after "restarting" the SQL Server Service, if the initial string contains 10 chars, it performs great too, but the performance degrades when we delete chars from the "query" string, the complete opposite.
Why is this happening?
When SQL server compiles the first execution plan, it optimizes it to perform really fast with a large result set (or viceversa). Subsequent queries, which narrows (or expands) the resultset, require a different execution plan ... BUT ... EF generated SQL uses commad parameters to (precisely) avoid statement recompiling ...
Cleaning the Execution Plan Cache by executing:
db.ExecuteStoreCommand("DBCC FREEPROCCACHE");
restores the performance to excellent response times ... BUT ... it kills al plans in all databases, thus degrading the performance of all other cached plans (which generally perform OK).
After doing some profiling on the EF sql statements, I executed DBCC FREEPROCCACHE in Query Analyzer prior to the sql EF generates, which turned out to generate different execution plans, all performing in the 250ms range, independently of the parameter length:
DBCC FREEPROCCACHE
exec sp_executesql N'SELECT TOP (50)
[Project1].[C1] AS [C1],
[Project1].[C2] AS [C2],
[Project1].[afpCUIT] AS [afpCUIT]
FROM ( SELECT
[Extent1].[afpCUIT] AS [afpCUIT],
[Extent1].[afpNombre] AS [afpNombre],
1 AS [C1],
RTRIM([Extent1].[afpNombre]) AS [C2]
FROM [dbo].[CONSTA] AS [Extent1]
WHERE [Extent1].[afpCUIT] LIKE @p__linq__0 ESCAPE N''~''
) AS [Project1]
ORDER BY [Project1].[afpNombre] ASC',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'2023291%'
THE QUESTION
Is there a more ellegant alternative to
db.ExecuteStoreCommand("DBCC FREEPROCCACHE");
?
Surprisingly, the second path of the query ( where u.afpNombre.StartsWith(query) ) are not affected by the same problem and performs great. Obviously, execution plans do not change when the lenght of the string changes...
I found an ObjectContext parameter in older versions of EF:
System.Data.EntityClient.EntityCommand.EnablePlanCaching
but I couldn't find it in EF4, and I'm not sure if the global results would be the same.
I'm really puzzled with this problem, and I don't know where the real problem lies
Poor index design? Lack of partitions? SQL SERVER 2008 Express edition? EF generated SQL? Lousy luck?
Any help would be great. Thanx in advance!
There's a way to remove a single plan from SQL Server's cache. It's explained in detail here: http://sqlblog.com/blogs/kalen_delaney/archive/2007/09/29/geek-city-clearing-a-single-plan-from-cache.aspx
Also, you can create a Stored Procedure, and map it with Entity Framework instead of using LINQ2Entities, and in this way make spcific changes to the SQL syntax, and make sure it's always the same.
这篇关于实体框架缓存的查询计划性能随着不同的参数而降级的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!