1

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

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:这个查询是在另一个函数中调用的,所以将结果放在一个临时表中,然后进一步过滤它们也是不可能的。

4

2 回答 2

0

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

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

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

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

于 2012-05-10T07:52:22.710 回答
0

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

编辑

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

于 2012-05-03T16:24:06.837 回答