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

Sql server 实体框架缓存的查询计划性能随参数的不同而降低,sql-server,performance,entity-framework-4,sql-execution-plan,sql-server-2008r2-express,Sql Server,Performance,Entity Framework 4,Sql Execution Plan,Sql Server 2008r2 Express,我有以下问题 背景 我正试图在一个有450万条记录的表上使用MVC3、EF4和jquery实现一个自动完成选择器 这是表格: 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) COL

我有以下问题

背景 我正试图在一个有450万条记录的表上使用MVC3、EF4和jquery实现一个自动完成选择器

这是表格:

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:

以下是记录描述:

以下是应用程序的代码:

控制器:

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);
        }
    } 
}
将性能恢复到卓越的响应时间。。。但是它会杀死所有数据库中的所有计划,从而降低所有其他缓存计划(通常执行正常)的性能

在对EF sql语句进行了一些评测之后,我在生成sql EF之前在查询分析器中执行了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? 运气不好

任何帮助都会很好。
先走一步

有一种方法可以从SQL Server的缓存中删除单个计划。 这里有详细的解释:


此外,您还可以创建一个存储过程,并将其映射到实体框架,而不是使用LINQ2Entities,通过这种方式对SQL语法进行特殊更改,并确保其始终保持不变。

正如您所指出的,SQL Server编译计划以针对一个具有大结果集的参数值进行优化。当结果集缩小时,查询的性能不好

此场景要求在查询中使用“option(recompile)”提示,因此将为收到的每个值重新编译查询

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

您可以在此处找到有关DbCommandInterceptor的信息-

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

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)'

如果您认为EF是问题的根源,那么可以通过存储过程轻松切换EF4。您是否使用了适当的SQL Server分析工具(如Quest)并检查了缓冲区刷新、磁盘I/O等?您是否考虑过增加SQL Server可用的RAM?已经考虑过了,但结果还是一样的。我试图对“query”参数的长度设置一个条件,但它只适用于相同的统计数据。。。关于服务器资源,它们不是问题,因为查询在笔记本电脑中执行得很好(只要查询计划没有缓存),非常感谢!Svarog,非常感谢,这个博客真的击中了要害(它提出了与我完全相同的问题),并为我提供了:a)一个“更优雅”的解决方法,我可以在开发过程中立即使用:使用:db.ExecuteStoreCommand(“DECLARE@myDb AS INT=db_ID();DBCC FLUSHPROCINDB”只刷新有问题的数据库进程(而不是整个服务器缓存)(@myDb)”;b)生产环境的解决方案,因为SQL EXPRESS不提供sp_创建_计划_指南。我不确定这在企业中是否是一个问题,但我开始认为这必须在生产中解决。非常感谢你!这个链接似乎已失效。是否可以找到相关信息,并将这些信息添加到答案中,而不仅仅是一个链接?看来@JerónimoVargas评论中的信息可能是所需要的全部,但这意味着存在更多信息。
System.Data.EntityClient.EntityCommand.EnablePlanCaching
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)'