标量值函数比预期慢

时间:2012-07-23 15:32:16

标签: performance sql-server-2005 user-defined-functions

CREATE function [dbo].[fn_GetDateOnly](@dateWithTime datetime)
    returns datetime WITH SCHEMABINDING
as
begin
    return DATEADD(DAY, DATEDIFF(DAY, 0, @dateWithTime), 0)
end


SELECT stuff, count(ff.id)
FROM dbo.foo AS ff 
where
--DATEADD(DAY, DATEDIFF(DAY, 0, ff.startDate), 0) <= @curdate --6700ms
--dbo.fn_GetDateOnly(ff.startDate) <= @curdate --9300ms
group by stuff
order by stuff desc

为什么调用fn_getdateonly比内联它慢得多? SQL是否内联函数调用?

1 个答案:

答案 0 :(得分:3)

列上的表达

首先,无论您使用什么函数,将其放在WHERE子句或表中列的JOIN条件中都是次优的。对常数进行数学运算并进行比较。您的WHERE子句应如下所示:

ff.startDate < DateAdd(day, 1, @curdate) -- if @curdate has time portion removed
ff.startDate < DateAdd(day, 1, dbo.fn_GetDateOnly(@curdate)) -- if @curdate has time

对于在给定日期一般查找项目,请使用以下模式:

WHERE
   DateCol >= '20120901'
   AND DateCol < '20120902'

将任何函数放在等号的另一侧作为列,它应该是唯一的。它可以帮助您查找如何使表达式SARGable。如果列必须位于两侧,则将所有表达式放在执行计划中的“左”输入的一侧(其数据首先出现,是LOOP JOIN的外循环或者是“表”的一侧) HASH JOIN)。例如,如果您尝试这样做:

WHERE dbo.fn_getDateOnly(A.DateCol) = dbo.fn_getDateOnly(B.DateCol)

然后假设A.DateCol在执行计划中排在第一位,将其切换为:

WHERE
   B.DateCol >= DateAdd(day, DateDiff(day, 0, A.DateCol), 0)
   AND B.DateCol < DateAdd(day, DateDiff(day, 0, A.DateCol), 0)

(或者使用下面函数的内联版本,但我发现它同样笨拙,因此没有额外的间接值)。

如果要在所涉及的表上频繁地进行这种查询,那么可能需要进行一些重新设计,要么添加一个持久的计算列,其中删除了时间部分(可能已编入索引),以分割日期时间分成日期和时间字段,或者将其存储为开始日期(如果您确实不需要日期时间)。

注意:当然,对dbo.fn_getDateOnly的引用可以简单地替换为DateAdd(day, DateDiff(day, 0, DateCol), 0)。此外,如果值具有datetime数据类型,则您可以DateCol + 1而不是使用DateAdd(但要小心,因为这不适用于date SQL 2008及更高版本中的数据类型。

UDF的内联能力

对于SELECT子句中函数的性能,SQL Server只内联“内联函数”,而不是标量函数。更改函数以返回单行记录集CREATE FUNCTION ... RETURNS TABLE AS RETURN (SELECT ...)并按如下方式使用它:

SELECT
   (SELECT DateValue FROM dbo.fn_GetDateOnly(Col)),
   ...

它与参数化视图没什么不同。

在WHERE子句中使用此内联版本将会非常笨拙。做DateDiff几乎就更好了。在SQL 2008中,当然只使用Convert(date, DateCol)仍然遵循有关计算表达式放置位置的规则(在与列相等的另一侧)

一定要投票给inline scalar UDFs on Microsoft Connect!你是唯一一个认为这个功能非常缺乏的人。