解决UDF性能问题 - 手动缓存

时间:2009-02-03 04:21:13

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

我的系统做了一些相当繁重的处理,我一直在攻击性能,以便让我能够在更短的时间内运行更多的测试运行。

我有很多情况下需要调用一个UDF,比如500万行(我几乎认为没有办法解决它)。

嗯,事实证明,有一种方法可以解决这个问题,当UDF通过一组比总行数小一些的不同参数调用时,它会带来巨大的性能提升。

考虑一个UDF,它接受一组输入并返回基于复杂逻辑的结果,但是对于超过5m行的输入集,例如,只有100,000个不同的输入,因此它只会产生100,000个不同的结果元组(我的特殊情况从利率到复杂的代码分配不等,但它们都是离散的 - 这种技术的基本点是你可以通过运行SELECT DISTINCT来简单地确定技巧是否有效。

我发现这样做:

INSERT INTO PreCalcs
SELECT param1
       ,param2
       ,dbo.udf_result(param1, param2) AS result
FROM (
    SELECT DISTINCT param1, param2 FROM big_table
)

当PreCalcs被适当地编入索引时,将其与:

的组合
SELECT big_table.param1
    ,big_table.param2
    ,PreCalcs.result
FROM big_table
INNER JOIN PreCalcs
    ON PreCalcs.param1 = big_table.param1
    AND PreCalcs.param2 = big_table.param2

你的表现得到了巨大的提升。显然,仅仅因为某些东西是确定性的,并不意味着SQL Server正在缓存过去的调用并重新使用它们,正如人们所想的那样。

你唯一需要注意的是允许NULL,然后你需要仔细修复你的连接:

SELECT big_table.param1
    ,big_table.param2
    ,PreCalcs.result
FROM big_table
INNER JOIN PreCalcs
    ON (
        PreCalcs.param1 = big_table.param1
        OR COALESCE(PreCalcs.param1, big_table.param1) IS NULL
    )
    AND (
        PreCalcs.param2 = big_table.param2
        OR COALESCE(PreCalcs.param2, big_table.param2) IS NULL
    )

希望这会有所帮助,任何类似的UDF技巧或重构查询性能都是受欢迎的。

我想问题是,为什么这样的手动缓存是必要的 - 这不是服务器知道函数是确定性的吗?如果它产生如此大的差异,并且如果UDF如此昂贵,为什么优化器不会在执行计划中执行它?

2 个答案:

答案 0 :(得分:3)

是的,优化器不会为您手动记忆UDF。在你可以用这种方式折叠输出设置的情况下,你的技巧非常好。

如果您的UDF参数是其他表的索引,并且UDF从这些表中选择值来计算标量结果,那么可以提高性能的另一种技术是将标量UDF重写为选择结果值的表值UDF超过所有潜在参数。

当我们基于UDF查询的表受到大量插入和更新时,我使用了这种方法,涉及的查询相对复杂,并且原始UDF必须应用的行数很大。在这种情况下,您可以在性能方面取得一些重大改进,因为表值UDF只需要运行一次,并且可以作为优化的面向集合的查询运行。

答案 1 :(得分:1)

SQL Server如何知道您在500万行内有100,000个离散组合?

通过使用PreCalcs表,您只需运行超过100k行的udf而不是500万行,然后再将其展开。

现有的优化者都无法识别这些有用的信息。 标量udf是一个黑盒子。

对于更实用的解决方案,我将使用执行udf调用的计算持久列。 因此,所有查询中都可以索引/包含它。

这更适合OLTP,也许......我查询一张桌子,以多种不同的方式实时交易现金和头寸,所以这种方法适合我每次都避免udf数学开销。