【问题标题】:SQL Server query performance mysterySQL Server 查询性能之谜
【发布时间】:2012-05-03 16:16:49
【问题描述】:

我今天开的会议太多了,但我认为我的大脑软件还在。 在努力提高某些查询的性能时,我遇到了以下谜团(表名和字段的释义):

SELECT X.ADId FROM
(
    SELECT DISTINCT A.ADId
    FROM P WITH (NOLOCK)
    INNER JOIN A WITH (NOLOCK) ON (P.ID = A.PId)
    INNER JOIN dbo.fn_A(16) AS VD ON (VD.DId = A.ADId)
    LEFT JOIN DPR ON (LDID = A.ADId)
    WHERE ((A.ADId = 1) OR ((HDId IS NOT NULL) AND (HDId = 1))) AND
           (P.PS NOT IN(5,7)) AND (A.ASP IN (2, 3))
) X
WHERE (dbo.fn_B(X.ADId, 16) = 1)

正如您将看到的,内部查询的内容大多是无关紧要的。 最初的重点是我想避免在每条记录上调用 fn_B(),因为它们包含 ADId 的重复值,所以我在内部做了一个 SELECT DISTINCT 然后过滤不同的记录。 听起来很合理吧?

谜团从这里开始……

内部查询返回 NO RECORDS(对于指定的参数)。 如果我注释掉“WHERE fn_B() = 1”,那么查询会在零时间内运行(并且不返回任何结果)。 如果我把它放回去,那么查询需要 6-10 秒,再次没有返回任何结果。

这似乎超过了常识,或者至少是我的常见 SQL 意识 :-) 如果内部查询没有返回数据,那么外部条件永远不应该被评估吗?

当然,我花时间检查了实际的执行计划,保存并非常仔细地比较了它们。它们 99% 相同,没有什么不寻常的地方,或者我认为是这样。

我玩弄了一些 CTE 以在第一个 CTE 中获取查询结果,然后将其传递给第二个 CTE,该 CTE 有一些保证不过滤任何记录的条件,然后在所有 CTE 之外评估 fn_B() 调用,但是行为完全相同。

还有其他变体,例如使用旧查询(可能以相同的值多次调用 fn_B())具有相同的行为。如果我删除条件,那么我在零时间内没有任何记录。如果我把它放回去,那么 10 秒内没有记录。

有什么想法吗?

感谢您的宝贵时间 :-)

PS1:我尝试使用一个简单的查询重现 tempdb 上的情况,但我无法实现。它只发生在我的实际桌子上。 PS2:这个查询是在另一个函数中调用的,所以将结果放在一个临时表中,然后进一步过滤它们也是不可能的。

【问题讨论】:

    标签: sql-server-2008 database-performance


    【解决方案1】:

    请注意,优化器不会以与您相同的方式读取查询。即使您认为应该执行某个顺序,或者短路可能是最有意义的,优化器仍然可能以您可能意想不到的顺序评估 CTE / 子查询。您可以尝试的解决方法是将第一个查询选择到 #temp 表中,然后在 #temp 表上运行函数过滤器。这应该强制执行评估顺序,即使它完全不直观且不那么优雅。

    编辑

    此外,虽然它可能执行得较慢,但我很好奇如果您在没有 NOLOCK 或 RCSI 的情况下运行查询会发生什么。不同的锁定语义可能会影响优化器。

    【讨论】:

    • 我想大多数人已经意识到优化器以奇怪的方式重新排列事物。但是有时查询是以这样的方式编写的,以便程序员负责发生的事情。如果我执行两个 SELECT DISCTINCT 然后加入它们,我大致确定会发生什么。无论如何,我今天将尝试一些调用,内部查询实际带来数据的地方,或者我用虚拟函数替换 fn_B() 的地方,只是为了看看行为如何变化。
    • 你会这么认为,但也有很多例外。优化器并不完美。你今天阅读了 Hugo 的博客文章和错误报告吗? sqlblog.com/blogs/hugo_kornelis/archive/2012/05/04/… 我见过几种情况,其中强制执行我期望从优化器获得的行为的唯一方法是将查询分解为单独的查询。正如我上面建议的那样,这只是一个你可以尝试的想法。
    • 额外信息。我使用返回 6 行的参数运行内部查询。零时间。添加 WHERE ==> 30 秒。我使用这些 ID 对 fn(B) 进行了 6 次显式调用,总共 0 秒。我把整个东西放在探查器中,这里给出了什么... SQL Server 开始在相同的 5 个表上一次又一次地进行表扫描雪崩(探查器日志中大约有 100.000 个条目),然后执行询问。所有这些表都出现在 fn_B() 中,在原始示例中从未调用过。删除 NOLOCK 没有任何区别。所以我开始认为这里的 SQL 服务器有些令人困惑。
    • 我不怀疑我/我们最终会找到解决办法。我的观点是,这看起来像是非常不正常的行为,所以我个人需要了解它为什么会发生。
    【解决方案2】:

    我们将该问题提交给了 Microsoft 对 SQL Server R2 的支持(我必须评论他们惊人的响应时间和整体服务程序)。我们给了他们一个重现问题的数据库副本,以及我们的解决方法,他们自己重现了它,几天后我们得到了答案:

    我已经分析了这两个执行计划,请问是否 解决方法可以在生产中使用吗?主要原因 在它的背后,是一个函数没有,因为索引有, 统计数据。而这种数据的缺乏使得优化器有时会选择 一个不太好的执行计划。如果您已经找到解决方法,那就是 最好实现这一点。我们尝试的索引更改并没有改善 执行。

    这是一种非常外交的方式来表达“是的,优化器将您的查询弄乱了,所以请使用解决方法”。如果你想称它为 bug,就称它为 bug,没关系。

    为了记录,解决方法是将调用 fn_B() 放在查询的 SELECT 列表中,比 SELECT DISTINCT 高一级,然后根据 WHERE 条件过滤其结果。有点奇怪,但它可以解决问题。

    【讨论】:

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