【问题标题】:SQL Parameter Slows Down QuerySQL 参数减慢查询速度
【发布时间】:2011-10-24 10:14:58
【问题描述】:

我有一个通过 ADO.NET 与 SQL Server 2008R2 一起使用的查询。当我使用内联 LIKE 子句时,它会在不到一秒的时间内工作,从 200 万返回 5 行。如果我像在 .NET 中那样在 SSMS 中的查询开始时声明参数,则需要很长时间。

这是同一个查询,但参数化了。

第一个(效果很好)是(效果很好):

;WITH Results_CTE AS (
    SELECT  ld.* , ROW_NUMBER() OVER (ORDER BY PK_ID) AS RowNum  
    FROM list..List_Data ld 
    WHERE Name IS NOT NULL  AND 
    Postcode LIKE 'SW14 1xx%' 
) SELECT * FROM Results_CTE 

需要永远的第二个是:

declare @postcode varchar(10) = 'SW14 1xx'
;WITH Results_CTE AS (
    SELECT  ld.* , ROW_NUMBER() OVER (ORDER BY PK_ID) AS RowNum  
    FROM list..List_Data ld
    WHERE Name IS NOT NULL  AND 
    Postcode LIKE @postcode +'%' 
) SELECT * FROM Results_CTE 

我相信这与 SQL Server 的内部工作原理有关,但我真的不知道。

【问题讨论】:

  • like 上搜索会使优化器不太可能使用索引。在第一种情况下,它可以看到字符串的开头没有通配符,因此它将使用索引。或许你可以hint表示应该使用索引。
  • @KlasLindbäck - SQL Server 将参数化的 LIKE 实现为范围搜索,因此在它具有非前导通配符的情况下,查询不会受到惩罚(在存在前导通配符的情况下range 是整个索引)。大概在这种情况下,虽然Postcode 上有一个非覆盖索引,但它没有使用它,因为它过度估计了需要的查找次数。
  • @Echilon:第二个查询的解释计划是什么?您可以尝试的一件事是在调用查询之前将“%”添加到postcode(以防+ '%' 混淆优化器)。应该没什么区别,但值得一试。
  • 在处理 SQL Server 性能问题时,您应该始终get an execution plan

标签: sql sql-server sql-server-2008 ado.net parameterized


【解决方案1】:

使用

SELECT * 
FROM Results_CTE 
OPTION (RECOMPILE)

SQL Server 不会嗅探变量的值,因此它不知道它的选择性有多大,并且可能会假设查询将返回比实际情况多得多的行,并为您提供优化的计划。

在您的情况下,我很确定在好的计划中您会发现它使用非覆盖非聚集索引来评估 PostCode 谓词和一些查找来检索丢失的列,而在坏计划中(如它猜测查询将返回更多的行数)它避免这种情况以支持全表扫描。

【讨论】:

  • 这个可以用来加速所有参数化查询吗?我们在查询时遇到了性能问题,该查询基本上是:WHERE DATECOL BETWEEN @D1 AND @D2,当我们将实际日期值作为文字插入 SQL 时,它的速度大大加快。我们可以在上面加上OPTION (RECOMPILE),它会工作得更快吗?我知道我应该对此进行测试,但我们重写了 SQL 层以自动注入这些日期,因此现在创建一个有效的测试有点痛苦。
  • @LasseV.Karlsen:重新编译查询不是免费的,所以这将是一个平衡的行为。在我使用的数据库上,重新编译每个查询会增加大约 30 毫秒。
  • 是的,但是由于我们将参数值注入到 SQL 中,所以我们基本上是在强制重新编译,不是吗?当值改变时,整个SQL都改变了,所以必须重新编译?
  • @Andomar 您需要弄清楚编译成本与使用不适当计划的成本 * 使用不适当计划的概率,看看是否值得。 @Lasse - 是的,在您的情况下,无论如何您都在编译(对于传递的每组不同的参数)并用类似的即席查询填充计划缓存,这些查询仅在参数值上有所不同。
  • @Echilon - 列的数据类型是什么?参数的数据类型是什么? (注意不要对varchar 列使用nvarchar 参数,因为这会导致隐式转换)See this answer for details
【解决方案2】:

我在谷歌上搜索 C# 中 SqlCommand.Parameters.Add() 的潜在问题,我找到了这个页面。我知道这是一篇 SQL Server 帖子,但其他人可能会通过 google 找到它,它可能会帮助他们使用 C#。

对我来说,以上答案都不起作用,所以我尝试了另一种方法。

代替:

cmd.Parameters.Add(new SqlParameter("@postcode", postcode));

我用这个代替:

// Replace SqlDbType enumeration with whatever SQL Data Type you're using.
cmd.Parameters.Add("@postcode", SqlDbType.VarChar).Value = postcode;

别忘了命名空间:

using System.Data;

希望这对某人有所帮助!

【讨论】:

  • 谢谢,伙计.. 这个对我有用。全力以赴解决参数嗅探问题,但没有运气。然而,这有所帮助。
  • 你也可以使用属性初始化器 new SqlParameter("@postcode", postcode) { SqlDbType = SqlDbType.VarChar }。 IMO 清洁剂。
【解决方案3】:

您可以使用optimize for 让参数化查询使用与具有特定参数的查询相同的执行计划:

SELECT * 
FROM Results_CTE 
OPTION (OPTIMIZE FOR (@postcode = 'SW14 1xx'))

【讨论】:

  • 这对我来说也很有希望。
【解决方案4】:

这看起来像是参数嗅探引起的问题 - 在计划编译期间 SQL Server “嗅探”当前参数值并使用它来优化查询。这可能导致的最常见问题是,如果查询在第一次运行/编译时使用“奇数”参数值运行,在这种情况下,查询计划将针对该参数值进行优化,但是参数嗅探可能会导致所有其他问题

在您的情况下,如果查询使用 @postcode 的空/null 值运行,则查询使用 LIKE '%' 子句,这很可能导致表扫描,因为正在使用 LIKE 通配符在过滤器的开头。看起来该计划最初是使用空的 @postcode 参数运行/编译的,或者 SQL Server 不知何故被此参数弄糊涂了。

您可以尝试以下几种方法:

  1. 将查询标记为重新编译,然后使用@postcode 的非空值再次运行查询。
  2. “屏蔽”参数以尝试防止参数嗅探,

例如:

declare @postcode varchar(10) = 'SW14 1xx'
declare @postcode_filter varchar(10) = @postcode + '%'
-- Run the query using @postcode_filter instead of @postcode

虽然这个查询看起来应该以完全相同的方式运行,但我发现 SQL Server 以奇怪的方式处理参数 - 关于何时使用参数嗅探的规则有时可能有点奇怪,所以你可能想要玩弄上述的变化。

【讨论】:

  • 发布的代码使用变量而不是参数。变量值仅在语句重新编译时被嗅探。实际上,该值被掩盖了导致 OP 出现问题的事实。
  • 谢谢,但这在 ADO.NET 中使用会很麻烦
猜你喜欢
  • 2011-10-04
  • 2015-11-27
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-28
相关资源
最近更新 更多