实体框架缓存查询计划性能随着不同参数而降低

时间:2011-11-01 05:28:49

标签: sql-server performance entity-framework-4 sql-execution-plan sql-server-2008r2-express

我有以下问题。

背景

我正在尝试使用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行认定。 当第一个“自动完成”字符串到达​​时(通常最多一个或两个字符),查询执行得异常快,但是,当字符串的长度增加时,性能会显着降低(在9或者大约需要20秒到2分钟之间)更多的角色)。 令人惊讶的是,在“重新启动”SQL Server服务之后,如果初始字符串包含10个字符,它的性能也很好,但是当我们从“查询”字符串中删除字符时,性能会下降,完全相反。

为什么会这样?

当SQL Server编译第一个执行计划时,它会优化它以使用大结果集(或反之亦然)执行得非常快。后续查询(缩小(或扩展)结果集)需要不同的执行计划......但是...... EF生成的SQL使用commad参数来(精确地)避免语句重新编译......

执行以下操作清理执行计划缓存:

db.ExecuteStoreCommand("DBCC FREEPROCCACHE");

将性能恢复到出色的响应时间......但是......它会杀死所有数据库中的计划,从而降低所有其他缓存计划(通常执行正常)的性能。

在对EF sql语句进行一些分析之后,我在sql EF生成之前在查询分析器中执行了DBCC FREEPROCCACHE,结果生成了不同的执行计划,所有执行计划都在250ms范围内执行,与参数长度无关: / p>

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提前!

2 个答案:

答案 0 :(得分:2)

有一种方法可以从SQL Server的缓存中删除单个计划。 这里详细解释: http://sqlblog.com/blogs/kalen_delaney/archive/2007/09/29/geek-city-clearing-a-single-plan-from-cache.aspx

此外,您可以创建存储过程,并使用实体框架而不是使用LINQ2Entities进行映射,并以这种方式对SQL语法进行特定更改,并确保它始终相同。

答案 1 :(得分:0)

正如您所确定的那样,SQL Server编译要针对具有大结果集的一个参数值进行优化的计划。结果集缩小时,查询效果不佳。

此方案需要在查询中使用“选项(重新编译)”提示,因此将针对其接收的每个值重新编译查询。

使用实体框架执行此操作并不容易。您需要创建一个DbCommandInterceptor以在查询中包含选项(重新编译)。另一个选择是在SQL Server中创建一个计划指南,以便为查询添加“选项(重新编译)”。

您可以在此处找到有关DbCommandInterceptor的信息 - Adding a query hint when calling Table-Valued Function

关于计划指南,您需要类似的内容:

EXEC sp_create_plan_guide   
'planguidename',   
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',
'SQL',   
NULL,   
N'@p__linq__0 nvarchar(4000)',
N'OPTION (recompile)'