2012-05-03 53 views
1

今天我参加了太多的会议,但我认为我还有自己的智能软件。 在我的努力来改善某些查询我碰到下面的神秘来到的表现(表名和字段转述):SQL Server查询性能谜

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,然后过滤了不同的记录。 听起来合理吗?

这里开始谜...

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

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

当然我花时间检查实际的执行计划,保存并仔细比较它们。它们是99%相同的,没有什么不寻常的可以注意到,或者我认为。

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

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

任何想法的人?

感谢您的时间:-)

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

回答

0

就像一个说明,优化器不会按照您的方式读取查询。即使您认为某个顺序应该发生,或者短路可能最有意义,优化程序仍然可以按照您预期的顺序评估CTE /子查询。您可能会尝试的解决方法是将第一个查询选择到#temp表中,然后在#temp表上运行函数过滤器。这应该强制评估的顺序,即使它是完全不直观的,也不那么优雅。

编辑

此外,虽然它可以执行慢,我很好奇,如果你没有NOLOCK运行查询,或RCSI而不是发生了什么。不同的锁定语义可能会跳过优化器。

+0

我想大多数人已经意识到优化器以奇怪的方式重新排列事物。但是有时候查询是以这样的方式编写的,以便程序员负责发生的事情。如果我做两个SELECT DISCTINCTs然后加入他们,我大致确定会发生什么。无论如何,今天我会尝试一些调用,其中内部查询实际带来数据,或者我用一个虚拟函数替换fn_B(),以查看行为是如何改变的。 –

+0

你会这么想,但有很多例外。优化器并不完美。你刚才读过雨果的博客文章和bug报告吗? http://sqlblog.com/blogs/hugo_kornelis/archive/2012/05/04/the-curious-case-of-the-optimizer-that-doesn-t.aspx我见过几种情况下唯一的方法强制我期望从优化器的行为是将查询分解成单独的查询。正如我上面所建议的那样,这只是一个你可以尝试的想法。 –

+0

额外信息。我使用返回6行的参数运行内部查询。零时间。添加WHERE ==> 30秒。我用这些ID对fn(B)进行了6次显式调用,总共0秒。我把整个事情放在探查器中,这里给出的结果是...... SQL Server开始在SAME 5表上一遍又一遍地扫描表扫描(一次又一次,在探查器日志中约有100.000个条目),然后执行查询。所有这些表都出现在fn_B()中,这在原始示例中永远不会被调用。删除NOLOCK并没有什么不同。所以我开始认为这里有一些令人困惑的SQL服务器。 –

0

我们将问题提交给Microsoft支持SQL Server R2(我必须评论他们惊人的响应时间和整体服务程序)。我们给了他们我们的数据库的副本再现问题,我们的解决办法,他们复制它自己,后一对夫妇在这里的天是我们回来了答案:

我分析这两个执行计划,并会亲切询问 解决方法是否可以接受用于生产?它后面的主要原因是 ,是一个函数没有的,因为索引有, 统计。而这种数据的缺乏使得优化器有时会选择一个不太好的执行计划。如果您已经找到了解决方法,那么最好实施这个方案是 。我们尝试的索引更改没有改进执行。

这是相当外交的方式来说“是的,优化程序与您的查询混淆了事情,所以请使用解决方法”。如果你想把它称为一个bug,把它称为一个bug,这没关系。

只是为了记录,解决方法是将调用fn_B()放入SELECT DISTINCT上一级查询的SELECT列表中,然后在WHERE条件中筛选其结果。有点奇怪,但它有诀窍。