Sql server 2008 SQL Server查询性能之谜

Sql server 2008 SQL Server查询性能之谜,sql-server-2008,database-performance,Sql Server 2008,Database Performance,今天我开了太多的会议,但我想我的大脑还是准备好了。 为了提高某些查询的性能,我遇到了以下解释的神秘表名和字段: 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

今天我开了太多的会议,但我想我的大脑还是准备好了。 为了提高某些查询的性能,我遇到了以下解释的神秘表名和字段:

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的重复值,所以我在内部选择了DISTINCT,然后过滤DISTINCT记录。 听起来很合理,对吗

这里开始神秘

内部查询不返回指定参数的任何记录。 如果我注释掉WHERE fn_B=1,那么查询将以零时间运行,并且不返回任何结果。 如果我把它重新打开,那么查询需要6-10秒,同样没有返回结果

这似乎超越了常识,或者至少是我的常识:- 如果内部查询没有返回任何数据,那么外部条件永远不会得到计算,对吗

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

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

其他变体,比如使用可能多次使用相同值调用fn_B的旧查询,也有相同的行为。如果我删除该条件,那么我在零时间内不会得到任何记录。如果我把它放回去,10秒内就没有记录了

有什么想法吗

感谢您抽出时间:-

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

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

编辑


另外,虽然它的执行速度可能较慢,但我很好奇,如果在没有NOLOCK的情况下运行查询,或者在RCSI中运行查询,会发生什么情况。不同的锁定语义可能会使优化器出错。

我们已将此问题提交给Microsoft SQL Server R2支持部门。我必须对他们惊人的响应时间和整体服务过程发表评论。我们给了他们一份复制问题的DB副本,我们的解决方案,他们自己复制了,几天后,我们得到了答案:

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

这是一种相当圆滑的方式,可以说是的,优化器会把你的查询搞得一团糟,所以请使用变通方法。如果你想叫它虫子,叫它虫子,没关系


对于记录,解决方法是将对fn_B的调用放在查询的SELECT列表中,比SELECT DISTINCT高一级,然后根据WHERE条件过滤其结果。有点奇怪,但它确实起到了作用。

我想大多数人已经意识到优化器以奇怪的方式重新安排事情。但有时查询是以这样的方式编写的,这样程序员就可以负责所发生的事情。如果我做两个选择盘然后加入它们,我大致确定会发生什么。无论如何,我今天将尝试一些调用,其中内部查询实际带来数据,或者我用一个伪函数替换fn_B,只是为了看看行为是如何变化的。你可能会这么认为,但也有很多例外。优化器并不完美。你今天有没有读过雨果的博客文章和bug报告?我见过几个例子,其中强制执行优化器行为的唯一方法是将查询分解为单独的查询。正如我上面所建议的,这只是一个你可以尝试的想法。额外信息。我使用返回6行的参数运行内部查询。零时间。添加WHERE==>30秒。我用这些ID给fnB打了6个明确的电话,总共0秒。我把所有的东西都放在分析器里,这里给出的是。。。SQLServer开始了一场雪崩
在相同的5个表上,一次又一次地扫描探查器日志中大约100.000个条目,然后执行查询。所有这些表都出现在fn_B中,在原始示例中从未调用过fn_B。移除NOLOCK没有什么区别。因此,我开始意识到有些东西让SQL server感到困惑。我不怀疑我/我们最终会找到解决方法。我的观点是,这看起来像是非常不正常的行为,所以我个人需要理解为什么会发生这种情况。