Sql server 如何提高SQL Server中纯用户定义函数的性能?

Sql server 如何提高SQL Server中纯用户定义函数的性能?,sql-server,sql-server-2008,sqlclr,user-defined-functions,memoization,Sql Server,Sql Server 2008,Sqlclr,User Defined Functions,Memoization,我制作了一个简单但计算相对复杂的UDF,用于查询很少更改的表。在典型用法中,该函数在一个非常小的参数域上从WHERE子句多次调用 我该怎么做才能更快地使用UDF?我的想法是,应该有某种方法告诉SQL Server,我的函数使用相同的参数返回相同的结果,因此应该进行记忆。在UDF中似乎没有这样做的方法,因为它们被要求是纯的,因此不能写入临时表 为了完整性,我的UDF如下所示,尽管我正在寻找一个关于如何加快在小型域上调用UDF的一般答案,而不是如何优化这个特定的UDF CREATE function

我制作了一个简单但计算相对复杂的UDF,用于查询很少更改的表。在典型用法中,该函数在一个非常小的参数域上从WHERE子句多次调用

我该怎么做才能更快地使用UDF?我的想法是,应该有某种方法告诉SQL Server,我的函数使用相同的参数返回相同的结果,因此应该进行记忆。在UDF中似乎没有这样做的方法,因为它们被要求是纯的,因此不能写入临时表

为了完整性,我的UDF如下所示,尽管我正在寻找一个关于如何加快在小型域上调用UDF的一般答案,而不是如何优化这个特定的UDF

CREATE function [dbo].[WorkDay] (
    @inputDate datetime, 
    @offset int) 
returns datetime as begin

declare 
     @result datetime 

set @result = @inputDate

while @offset != 0
begin
    set @result = dateadd( day, sign(@offset), @result )

    while ( DATEPART(weekday, @result ) not between 2 and 6 )
      or @result in (select date from myDB.dbo.holidays
      where calendar = 'US' and date = @result)
    begin
        set @result = dateadd( day, sign(@offset), @result )
    end
    set @offset = @offset - sign(@offset)
end
return @result

END

您可以写入一个真正的表,该表由您的参数键控,并首先选择该参数,如果该参数为空,则计算并插入到表中,执行您自己的缓存

在表中预先填入您感兴趣的日期范围的所有可能值,然后加入该范围,这可能更有意义。然后,对每个参数组合只进行一次计算,并让SQL处理联接。

我的第一个想法是——性能问题是什么?确保在运行查询的循环中有一个循环(每行应用一次)。但是你的执行计划很差吗?你的结果集很大吗?但让我们转向通用的。如何解决这个问题?SQL并不是真正的内存化(正如著名的@Martin_Smith指出的那样)。那么一个男孩该怎么办

选项1-新设计

创造一个全新的设计。在这个具体案例中,@Aaron_Bertrand指出,日历表可能满足您的需要。完全正确。这并不能真正帮助您处理非日历的情况,但在SQL中,您需要考虑一些不同的情况

选项2-减少自定义项调用

缩小调用此函数的项集。这让我想起了很多成功的秘诀。生成一个具有所需不同值的小结果集,然后调用您的UDF,以便只调用几次。这可能是一个选项,也可能不是一个选项,但可以在许多场景中使用

选项3-动态自定义项

我很可能会因为这个建议而被嘘出房间,但事情是这样的。使这个UDF变慢的是循环中的select语句。如果你的假日餐桌真的很少更换,你可以在餐桌上扣动扳机。触发器将写出并更新UDF。新的UDF可以强制执行所有的假日决定。用SQL编写SQL会有点像食人族吗?当然但它将摆脱子查询并加快UDF的速度。让质问开始吧

选项4-将其记忆起来

虽然SQL不能直接记忆,但我们有SQL CLR。将UDF转换为SQL CLR UDF。在CLR中,您可以使用静态变量。您可以很容易地以一定的间隔获取Holidays表并将其存储在哈希表中。然后在CLR中重写你的循环。如果符合逻辑,你甚至可以进一步把整个答案记下来


更新:

选项1-我在这里真正想关注的是一般函数,而不是上面使用的示例函数。但是,当前的UDF设计允许在您碰巧连续调用几个时,对Holiday表进行多个调用。使用某种日历样式的表格,其中包含“坏日子”列表和相应的“下一个工作日”,可以消除多次点击和查询的可能性

选项3-虽然域名是未知的提前你可以很好地修改你的假期表。对于给定的假日,它将包含下一个相应的工作日。从这些数据中,你可以写出一个底部有一个长案例陈述的UDF(当“2012年5月5日”然后是“2012年5月14日”或类似的时候)。此策略可能不适用于所有类型的问题,但可能适用于某些类型的问题

备选方案4——每项技术都有影响。需要部署CLR,修改SQL Server配置,并且SQL CLR仅限于3.5框架。就我个人而言,我发现这些调整很容易,但您的情况可能会有所不同(例如,顽固的DBA,或者对修改生产服务器的限制)

使用静态变量需要程序集。你必须确保你的锁正确


在非常高的事务级别上,CLR的性能不如直接SQL。然而,在您的场景中,这个观察结果可能不适用,因为您尝试执行的操作(memoize)没有直接的SQL关联

您可以使用日历表—它将比多语句标量UDF中的所有循环更有效。你真的不能。它将始终按行调用。如果可能的话,不要使用标量UDF(特别是那些像您一样进行数据访问的UDF)并找到其他方法。在可能的情况下,转换成内联TVF有时会有所帮助。@MartinSmith,现在开始阅读内联TVF。@Roger-希望你能找到@MartinSmith,阅读TVF。不幸的是,我的UDF需要像T-SQL这样的东西,并且与纯SQL查询一样会很尴尬。这种方法的问题是,虽然我在实践中使用的领域很小,但它在时间之前是未知的,并且可能是巨大的。在我的UDF中,每个日期可能有2^32个唯一的偏移量。SQL Server在数学方面非常好。在date和date+x之间计算表中的行应该非常容易,而不管您可能期望有几十亿个不同的偏移量。我强烈建议您尝试一下这种方法,而不是假设它不会比您现在所做的更快(这不是SQL Server擅长的)