【问题标题】:Weird SQL Server query problem奇怪的 SQL Server 查询问题
【发布时间】:2009-04-22 08:45:14
【问题描述】:

所以我在 SQL Server 存储过程中遇到了这个奇怪的问题。基本上我有这个漫长而复杂的过程。像这样的:

SELECT Table1.col1, Table2.col2, col3
FROM Table1 INNER JOIN Table2
     Table2 INNER JOIN Table3
     -----------------------
     -----------------------
     (Lots more joins)
WHERE Table1.Col1 = dbo.fnGetSomeID() AND (More checks)
     -----------------------
     -----------------------
(6-7 More queries like this with the same check)

问题是签入 WHERE 子句末尾 Table1.Col1 = dbo.fnGetSomeID()。函数 dbo.fnGetSomeID() 返回一个简单的整数值 1。因此,当我对函数调用应该是 SP 的值 1 进行硬编码时,只需要大约 15 秒。但是当我用 WHERE 子句中的那个函数调用替换它时,大约需要 3.5 分钟。

所以我这样做:

DECLARE @SomeValue INT
SET @SomeValue = dbo.fnGetSomeID()
--Where clause changed
WHERE Table1.Col1 = @SomeValue

所以现在这个函数只被调用一次。但还是一样的3.5分钟。所以我继续这样做:

DECLARE @SomeValue INT
--Removed the function, replaced it with 1
SET @SomeValue = 1
--Where clause changed
WHERE Table1.Col1 = @SomeValue

仍然需要 3.5 分钟。为什么会影响性能?以及如何让它消失?

【问题讨论】:

  • 自行执行dbo.fnGetSomeID()需要多长时间
  • 但这应该没关系,因为我最后用硬编码值替换。
  • 第二个应该更快。通常,您不希望 WHERE 子句中的函数,因为它们会针对结果集中的每个潜在行进行重新评估。

标签: sql sql-server sql-server-2005 stored-procedures


【解决方案1】:

即使 @SomeValue 设置为 1,当你有

WHERE Table1.Col1 = @SomeValue

SQL Server 可能仍将 @SomeValue 视为变量,而不是硬编码的 1,这会相应地影响查询计划。并且由于 Table1 链接到 Table2,Table2 链接到 Table3 等等,因此运行查询的时间量被放大了。另一方面,当你有

WHERE Table1.Col1 = 1

查询计划被 Table1.Col1 锁定为常数值 1。仅仅因为我们看到了

WHERE Table1.Col1 = @SomeValue

作为“硬编码”,并不意味着 SQL 以同样的方式看待它。每个可能的笛卡尔积都是候选者,需要对每个可能的 @SomeValue 进行评估。 因此,标准建议适用 - 检查您的执行计划,如果需要,重写查询。

另外,这些连接列是否已编入索引?

【讨论】:

    【解决方案2】:

    正如在其他地方提到的,执行计划会有所不同,具体取决于您采用的方法。我会查看两个执行计划,看看那里是否有明显的答案。

    This question 描述了一个类似的问题,而那个案例的答案竟然涉及到连接设置。

    我自己也遇到了几乎 exact same problem,在这种情况下我发现使用较新的构造(分析函数在 SQL 2008 中)显然使优化器感到困惑。由于您使用的是 SQL 2005,因此您可能不是这种情况,但根据查询的其余部分,可能会发生类似的情况。

    要查看的另一件事是您是否对 Table1.Col1 的值分布有偏差 - 如果优化器在您使用函数或变量而不是常量时使用一般执行计划,这可能会导致它选择次优连接,而不是当它可以清楚地看到该值是一​​个特定的常数时。

    如果所有其他方法都失败了,并且此查询不在另一个 UDF 中,您可以像以前一样预先计算 fnGetSomeID() UDF 的值,然后将整个查询包装在动态 SQL 中,将值作为 SQL 字符串中的常量提供.这应该会给您带来更快的性能,但代价是每次都重新编译查询(在这种情况下这应该是一个很好的交易)。

    【讨论】:

      【解决方案3】:

      另一件事要尝试。 与其将 id 加载到变量中,不如将其加载到表中

      if object_id('myTable') is not null drop myTable
      select dbo.fnGetSomeID() as myID into myTable
      

      然后使用

      WHERE Table1.Col1 = (select myID from myTable)
      

      在您的查询中。

      【讨论】:

        【解决方案4】:

        您可以尝试使用 OPTIMIZE FOR 提示来强制为给定常量制定计划,但结果可能不一致;在 2008 年,您可以使用 OPTIMIZE FOR UNKNOWN

        【讨论】:

          【解决方案5】:

          我认为由于优化器不知道函数做了多少工作,它会尝试最后评估它们。

          我会尝试提前将函数的返回值存储在一个变量中,并在你的 where 子句中使用它。

          另外,您可能想尝试模式绑定您的函数,因为显然有时它seriously affects peformance.

          你可以像这样绑定你的函数模式:

          create function fnGetSomeID()
          with schema_binding
          returns int
          ... etc.
          

          【讨论】:

            【解决方案6】:
             (Lots more joins)
            

            WHERE Table1.Col1 = dbo.fnGetSomeID() AND(更多检查)

            这不是一个好问题。最后,该值是由函数或子查询或变量返回还是常量都无关紧要。但确实如此,而且在某种程度的复杂性下,很难获得一致的结果。而且您无法真正调试它,因为您和这里的任何其他人都无法查看查询优化器的黑匣子。你所能做的就是戳它,看看它是如何表现的。

            我认为查询优化器的行为不正常,因为查询中有很多表。当您告诉它查找1 时,它会查看索引统计信息并做出不错的选择。当您告诉它其他任何内容时,它会假定它应该根据它 所做的事情 加入 知道,不相信您的函数/变量会返回选择性值。为此,Table1.Col1 必须具有不均匀的值分布。或者查询优化器不是,嗯,最优的。

            无论哪种方式,估计的查询计划都应该有所不同。寻找添加(或有时删除)索引的机会。可能是 3.5 计划在很多情况下是合理的,而服务器真正想要的是更好的索引。

            除此之外就是猜测。有时,可悲的是,答案在于找到产生一小组行的表子集,将它们放入临时表中,然后将其连接到其余表中。 OPTIMIZE FOR 提示也可能有用。

            但请记住,您提供的任何解决方案都将是脆弱的,取决于数据和版本。

            【讨论】:

              猜你喜欢
              • 2011-07-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多