【问题标题】:How does SQL server evaluate the cost of an execution plan which contains a user defined function?SQL Server 如何评估包含用户定义函数的执行计划的成本?
【发布时间】:2009-09-25 14:02:53
【问题描述】:

我有一个存储过程,它根据 DATEADD 函数的结果进行过滤 - 我的理解是,这类似于使用用户定义的函数,因为 SQL 服务器无法根据它所拥有的函数的输出存储统计信息难以评估执行计划的成本。

查询看起来有点像这样:

SELECT /* Columns */ FROM
TableA JOIN TableB
ON TableA.id = TableB.join_id
WHERE DATEADD(hour, TableB.HoursDifferent, TableA.StartDate) <= @Now

(因此无法预先计算DATEADD 的结果)

我看到的是一个可怕的执行计划,我认为这是由于 SQL 服务器错误地估计从树的一部分返回的行数为 1,而实际上它约为 65,000。然而,当数据库中存在不同(不一定更少)数据时,我看到相同的存储过程在很短的时间内执行。

我的问题是 - 在这种情况下,查询优化器如何估计函数的结果?

更新:仅供参考,我更感兴趣的是了解为什么有时我会得到一个好的执行计划,而为什么其他时间却没有 - 我已经有了一个很好的执行计划从长远来看,我将如何解决这个问题。

【问题讨论】:

  • DATEADD 不是用户定义的函数。内置系统函数的处理方式通常与用户定义的函数不同。

标签: sql-server sql-server-2005 user-defined-functions sql-execution-plan


【解决方案1】:

这里的问题不在于计划的成本。列上的函数阻止 SQL 进行索引查找。您将进行索引扫描或表扫描。

我的建议是看看你是否可以从函数中取出一列,基本上看看你是否可以将函数移动到等式的另一边。这并不完美,但这意味着至少有一列可用于索引查找。

类似这样的东西(粗略的想法,未经测试),在 TableB.HoursDifference 上有一个索引,然后在 TableA 中的连接列上有一个索引

DATEDIFF(hour, @Now, TableA.StartDate) >= TableB.HoursDifferent

在成本计算方面,我怀疑优化器会使用 30% 的表“thumb-suck”,因为它无法使用统计数据来获得准确的估计,并且因为它是一个不等式。这意味着它将猜测该谓词将返回 30% 的表。

如果没有看到执行计划,真的很难说什么。您提到了 1 行的估计值和 65000 的实际值。在某些情况下,这根本不是问题。 http://sqlinthewild.co.za/index.php/2009/09/22/estimated-rows-actual-rows-and-execution-count/

【讨论】:

  • 它确实在进行表/索引扫描,但是每个表只有少数条目(例如 600 左右) - 问题是因为 SQL 服务器最终进行了 ~65,000 次 RDI 查找(在一个只包含 600 行的表!)。再次抱歉,我无法向您展示执行计划,但如果不了解整个上下文就没有多大意义,就像我说的那样,这涉及 10 个不同的表、250 行存储过程和大量索引。
【解决方案2】:

查看函数会有所帮助,但我看到的一件事是在查询中隐藏这样的函数会导致性能下降。如果您可以事先评估其中的一些,您可能会处于更好的状态。例如,而不是

WHERE MyDate < GETDATE()

试试

DECLARE @Today DATETIME
SET @Today = GETDATE()
...
WHERE MyDate < @Today

这似乎表现更好

【讨论】:

  • 因为当列在函数内时,SQL 无法准确估计受影响的行数。当列不在函数内时,它可以使用列统计信息来获得相当好的估计
【解决方案3】:

@克拉根,

简答:如果您要对十个表进行查询,习惯它。您需要了解所有关于查询提示的知识,以及更多技巧。

长答案:

SQL 服务器通常只为最多大约三到五个表生成出色的查询计划。根据我的经验,一旦超越了这一点,您基本上将不得不自己编写查询计划,使用所有索引和连接提示。 (此外,标量函数的估计成本似乎为零,这简直是疯了。)

原因是在那之后它太复杂了。查询优化器必须在算法上决定要做什么,即使是 SQL Server 团队中最聪明的天才也有太多可能的组合来创建一个真正通用的算法.

他们说优化器比你聪明。这可能是真的。 但你有一个优势。这个优势就是如果它不起作用,你可以把它扔掉再试一次!大约在第六次尝试时,如果您知道数据,即使是十表连接,您也应该有一些可以接受的东西。查询优化器无法做到这一点,它必须立即制定某种计划,并且没有第二次机会。

我最喜欢的技巧是通过将 where 子句转换为 case 语句来强制它的顺序。而不是:

WHERE
predicate1
AND predicate2
AND....

使用这个:

WHERE
case 
when not predicate1 then 0
when not predicate2 then 0
when not .... then 0
else 1 end = 1

从最便宜到最贵的谓词排序,您会得到逻辑上相同但 SQL 服务器不会乱用的结果 - 它必须按照您说的顺序执行它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 2011-08-25
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    相关资源
    最近更新 更多