2

我有一个存储过程,根据DATEADD函数的结果进行过滤 - 我的理解是,这与使用用户定义的函数类似,因为SQL服务器无法存储基于该函数输出的统计信息,因此无法评估执行计划的成本。SQL Server如何评估包含用户定义函数的执行计划的成本?

查询看起来有点像这样:

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。然而,我看到相同的存储过程在数据库中存在不同的(不是少见的)数据的时间的一小部分时间内执行。

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

更新:仅供参考,我更感兴趣的是理解为什么有些时候我得到一个好的执行计划,为什么剩下的时间我没有 - 我已经有了一个很好的主意,长远来看,这将会解决这个问题。

+0

DATEADD不是用户定义的函数。内置的系统功能通常与用户定义的功能不同。 – GilaMonster 2009-09-25 14:44:43

回答

1

这将有助于看到该功能,但我所看到的一件事就是在查询中埋入这样的功能可能会导致性能下降。如果你可以预先评估一些,你可能会变得更好。例如,而不是

WHERE MyDate < GETDATE() 

尝试

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

这似乎有更好的表现

+0

我仍然不明白为什么这个问题立即解决了这个问题...... – Justin 2009-09-25 18:45:46

+1

由于SQL无法准确地估计列在函数中时受影响的行数。当列不在函数内时,它可以使用列统计信息来获得相当好的估计结果 – GilaMonster 2009-09-26 07:55:18

3

这并不是说这里的问题计划的成本核算。列上的函数阻止SQL执行索引查找。您将获得索引扫描或表扫描。

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

像这样的东西(粗略的想法,未测试)在表A

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

在成本核算侧TableB.HoursDifference索引,然后索引连接列,我怀疑优化器将使用表中“30%”的30%,因为它无法使用统计数据来获得准确的估计值,并且因为它是不平等的。这意味着它会猜测该表的30%将由该谓词返回。

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

+0

它确实在执行表/索引扫描,但是每个表只有少量条目(例如600左右) - 问题是由于SQL服务器最终会执行大约65,000次RDI查找(在只包含600行的表上!)。我再次抱歉,我不能向您展示执行计划,但是如果不知道整个上下文,那么它就没有多大意义,就像我说的那样,涉及10个不同的表,250行存储过程和大量索引。 – Justin 2009-09-25 15:49:30

1

@Kragen,

简短的回答:如果使用十张桌子做查询,习惯它。你需要了解所有关于查询提示的知识,以及更多的技巧。

龙答:

SQL服务器通常生成最多只有三到五桌出色的查询计划。一旦你超越了我的经验,你将基本上必须自己编写查询计划,使用所有的索引和联接提示。 (另外,Scalar函数似乎估计成本=零,这只是疯狂。)

原因是它太复杂了,太复杂了。查询优化器必须决定要做什么算法上,即使是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服务器未得到周围的混乱 - 它必须按照你说的顺序去做。