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

Posted

技术标签:

【中文标题】解决 UDF 性能问题 - 手动缓存【英文标题】:Working around UDF Performance Issues - Manual caching 【发布时间】:2009-02-03 04:21:12 【问题描述】:

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

在很多情况下,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 如此昂贵,那么优化器为什么不直接在执行计划中进行呢?

【问题讨论】:

我想问题是,为什么需要像这样的手动缓存 - 这不是服务器知道函数是确定性的吗?如果它产生了如此大的差异,并且如果 UDF 如此昂贵,那么优化器为什么不直接在执行计划中进行呢。 【参考方案1】:

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

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

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

【讨论】:

是的,不幸的是,所有潜在的参数都是问题所在。我正在尝试重新编写其中的一些代码,使其成为本机表驱动的。但在其他情况下,原始逻辑非常难以重构。我发现 UDF 的性能非常差,到了无用的地步。【参考方案2】:

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

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

现有的优化器无法预测这些有用的信息。 标量 udf 是一个黑盒子。

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

这可能更适合 OLTP...我查询表格以通过多种不同方式实时获取交易现金和头寸,因此这种方法适合我避免每次都产生 udf 数学开销。

【讨论】:

嗯,它当然知道,因为它被称为 UDF 5m 次。如果从不使用 UDF 来避免使用相同的参数一遍又一遍地调用它,那么数据库知道 UDF 是确定性的有什么意义? 现在我可以理解它是否只缓存了一定数量的结果并且存在缓存未命中,但它似乎基本上什么也没做。 它不缓存。确定性意味着相同输入的相同输出(足够接近),但行到行(500 万)这不会持续存在。优化器不会保留此信息”。 另外,你说它为相同的输入提供相同的输出。 SQL知道吗? SELECT OBJECTPROPERTYEX(OBJECT_ID('udf_result'), 'IsDeterministic') 是的,我所有的 UDF 都是确定性的 - 但显然这些信息没有用于任何事情。

以上是关于解决 UDF 性能问题 - 手动缓存的主要内容,如果未能解决你的问题,请参考以下文章

UDF 与子查询性能问题

通过UDF使mysql主动刷新redis缓存

内联表重视 UDF 性能

Hive UDF 性能太慢

SQL Server 2005 标量 UDF 性能

用于 UDF 性能统计的 MS SQL DMV - 如何找到前 10 个最差的 UDF