【发布时间】:2011-12-19 06:03:52
【问题描述】:
我有以下问题。
背景
我正在尝试使用 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]
([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
该表是相当静态的(它只需要每月批量更新)并且是只读的。
如果有人想下载记录 (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;
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);
}
}
}
查看:
@{
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>
现在:
问题
如您所见,如果查询字符串仅包含数字,则代码遵循不同的路径。
当控制器参数的所有字符都是数字时(其中 u.afpCUIT.StartsWith(query) ),查询优化器“应该”执行聚集索引查找(它会执行)并返回前 50 行发现。 当第一个“自动完成”字符串到达时(通常最多一个或两个字符),查询执行得非常快,但是,当字符串的长度增加时,性能会显着下降(它需要 20 秒到 2 分钟,而 9 或更多字符)。 令人惊讶的是,在“重新启动”SQL Server 服务后,如果初始字符串包含 10 个字符,它的性能也很好,但是当我们从“查询”字符串中删除字符时,性能会下降,完全相反。
为什么会这样?
当 SQL Server 编译第一个执行计划时,它会对其进行优化,以在大型结果集(反之亦然)的情况下快速执行。缩小(或扩展)结果集的后续查询需要不同的执行计划......但是...... EF 生成的 SQL 使用逗号参数(精确)避免语句重新编译......
通过执行清理执行计划缓存:
db.ExecuteStoreCommand("DBCC FREEPROCCACHE");
将性能恢复到极好的响应时间......但是......它会杀死所有数据库中的所有计划,从而降低所有其他缓存计划的性能(通常执行良好)。
在对 EF sql 语句进行了一些分析之后,我在 EF 生成 sql 之前在查询分析器中执行了 DBCC FREEPROCCACHE,结果生成了不同的执行计划,所有执行计划都在 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 速成版? EF生成的SQL? 运气不好?
任何帮助都会很棒。 提前谢谢!
【问题讨论】:
-
如果您认为 EF 导致了问题,您可以通过存储过程轻松切换 EF4。您是否使用过适当的 SQL Server 分析工具(如 Quest)并检查过缓冲区刷新、磁盘 I/O 等?您是否考虑过增加可供 SQL Server 使用的 RAM?
-
已经做到了,但结果同样蹩脚。我很想对“查询”参数的长度设定条件,但它只能使用相同的统计数据......关于服务器资源,它们不是问题,因为查询在笔记本电脑中表现出色(如只要查询计划没有被缓存)还是非常感谢!
标签: sql-server performance entity-framework-4 sql-execution-plan sql-server-2008r2-express