sp_executesql 中查询的执行时间很长

use*_*827 4 performance sql-server dynamic-sql execution-plan query-performance

我有一个从 .net 应用程序生成的查询,参数是 linq 参数。我用来调整查询的过程如下。

  • 我运行应用程序并使用探查器捕获查询。
  • 查询的形式是

    exec sp_executesql N'SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[GroupId] AS [GroupId], 
    [Extent1].[Agency] AS [Agency], 
    [Extent1].[Group] AS [Group], 
    [Extent1].[Claim] AS [Claim], 
    [Extent1].[StartDate] AS [StartDate], 
    [Extent1].[ExpireDate] AS [ExpireDate], 
    [Extent1].[IsActive] AS [IsActive]
    FROM [dbo].[Permissions] AS [Extent1]
    WHERE ([Extent1].[GroupId] = @p__linq__0) AND (([Extent1].[ExpireDate] IS NULL)
    OR ([Extent1].[ExpireDate] > @p__linq__1)) AND ([Extent1].[IsActive] <> 1)',
    N'@p__linq__0 int,@p__linq__1 datetime2(7)',
    @p__linq__0=15,@p__linq__1='2017-05-10 00:00:00'
    
    Run Code Online (Sandbox Code Playgroud)

这只是一个示例查询。实际查询要大得多,并且包括许多涉及多个视图和表等的连接。

我的问题是:

  • 按原样运行查询时,执行时间约为 15 秒
  • 当我单独取出查询sp_executesql并将过滤器值应用于查询时,它会在不到 1 秒的时间内运行。

我知道由于这些值是硬编码到查询中的,所以它会更快,但为什么执行时间会有这么大的差异?两者的执行计划也不同:

带有 sp_executesql 的 SQL 执行计划

使用硬编码 SQL 的执行计划

我是在这台服务器上工作的唯一用户,我在两种情况下都执行相同的参数。我已经申请OPTION(RECOMPILE)到最后,sp_executesql与 15 秒相比,它在 1 秒内执行。但是执行计划仍然与硬编码计划不相似。

无论如何OPTION(RECOMPILE)使查询在一秒钟内快速运行,因为它强制查询每次编译和创建新计划。但我不应该破解代码并更改查询。我会建议OPTION(RECOMPILE)我的申请团队作为最后的手段。

Joe*_*ish 5

查询优化器能够对动态查询使用参数嗅探,如实际计划中所示:

<ParameterList>
  <ColumnReference Column="@p__linq__5" ParameterDataType="varchar(8000)" ParameterCompiledValue="'MI3300287'" ParameterRuntimeValue="'MI3300287'" />
  <ColumnReference Column="@p__linq__4" ParameterDataType="datetime2(7)" ParameterCompiledValue="'2009-06-11 23:59:00.0000000'" ParameterRuntimeValue="'2009-06-11 23:59:00.0000000'" />
  <ColumnReference Column="@p__linq__3" ParameterDataType="datetime2(7)" ParameterCompiledValue="'2008-06-11 00:00:00.0000000'" ParameterRuntimeValue="'2008-06-11 00:00:00.0000000'" />
  <ColumnReference Column="@p__linq__2" ParameterDataType="varchar(8000)" ParameterCompiledValue="'MI3300287'" ParameterRuntimeValue="'MI3300287'" />
  <ColumnReference Column="@p__linq__1" ParameterDataType="int" ParameterCompiledValue="(90495)" ParameterRuntimeValue="(90495)" />
  <ColumnReference Column="@p__linq__0" ParameterDataType="int" ParameterCompiledValue="(90495)" ParameterRuntimeValue="(90495)" />
</ParameterList>
Run Code Online (Sandbox Code Playgroud)

这些嗅探到的值会影响选择的查询计划。但是,查询计划仍然需要对输入变量的所有可能值都是安全的。让我们看一下计划之间的一个区别,即IncidentDetailsPage_Header桌子上的访问权限。在较慢的动态计划中,您将获得扫描和哈希匹配,总共需要大约 2.4 秒:

慢速哈希匹配

在使用硬编码值的更快计划中,您会得到一个索引查找,而不是大约需要 0 毫秒:

快速索引查找

这是与计划的那部分相对应的 T-SQL:

 AND (([Extent1].[agencyori] = @p__linq__5)
      OR (([Extent1].[agencyori] IS NULL)
          AND (@p__linq__5 IS NULL)))
Run Code Online (Sandbox Code Playgroud)

对这些值进行硬编码会生成仅针对这些参数值的单次使用查询计划。使用您的参数值,该 T-SQL 可以简化为:

([Extent1].[AgencyOri] = 'MI3300287')
Run Code Online (Sandbox Code Playgroud)

这符合索引查找的条件。其他代码不是。尽可能避免厨房水槽查询