内联表值UDF性能

时间:2011-08-02 20:26:13

标签: sql-server inline sql-server-2008-r2 user-defined-functions

我正在使用SQL Server 2008R2。我编写了下面的表值UDF,它接受标量值this或that或者两者作为参数,并返回一个包含列ID,this和this的表。当我从复杂查询中调用此函数时,我看到了糟糕的性能,但是当我在简单查询中调用它时却没有。我想知道是否有人对我正在做的事情有任何想法,这会让事情变得缓慢。功能定义如下:

CREATE function dbo.fn_getThisThat (@this nvarchar(255), @that nvarchar(255))
RETURNS TABLE
RETURN

SELECT These.this, Those.that, COALESCE(These.ID, Those.ID) as ID
FROM 
    (
    SELECT col1 as ‘this’, value1, value2, ID
    FROM (
        SELECT t1.col1, t1.col2, t1.col3, t2.col1
        FROM t1
        JOIN t2
            ON t1.col1 = t2.col1
        WHERE t2.col2 = ‘this’
        AND t1.col1 in (‘value1’, ‘value2’)
        ) SOURCE
    PIVOT (
        MAX(t1.col3) FOR t1.col1 in (value1, value2)
        ) AS pvt
    ) These
JOIN
    (
    SELECT t1.col1, t1.col2, t2.col1, t3.ID
    FROM t3
    JOIN t1
        ON t3.col1 = t1.col1
    JOIN t2
        ON t2.col1 = t1.col1
    WHERE t3.col3 = ‘value3’
    AND t1.col3 = ‘value1’
    AND t2.col3 = ‘value2’
    ) Those
WHERE that = @that
OR this = @this

以下语句在传递标量参数时非常快速(<1秒):

SELECT * FROM dbo.fn_getThisThat(scalarValue, null)

或者在一个相对简单的查询中:

SELECT t1.col1, t1.col2, fn.This
FROM t1
CROSS APPLY dbo.fn_getThisThat(t1.col3, null)

...但是当它在这样一个更复杂的查询中调用时,它会滞后(从处理时间〜1秒到~2:30秒)(在伪代码中:如果信息不够,请告诉我):

DECLARE @table (a, b, c)
INSERT @table (a, b, c)
SELECT (values)

SELECT t1.c1, t1.c2, t1.c3
FROM
    (
    SELECT a.c1, COUNT(fn.That) as c2, COUNT(a.c2) as c3
    FROM a
    JOIN b ON (join terms)
    CROSS APPLY dbo.fn_getThisThat(a.c2, null) fn
    WHERE a.c1 IN (SELECT a FROM @table)
    GROUP BY a.c1
    ) t1

有没有人对我在第二次查询中的速度有什么建议?我将函数更改为接受数组而不是标量参数,但这消除了我交叉应用的能力(在最后一个代码片段中)。从查询分析器中我可以看出,性能命中来自我的函数的交叉应用。我以为我不会遇到RBAR因为我的UDF不是多语句,但也许我可怕的错误......?

编辑: 还有一件事:查询执行计划显示函数本身只对批处理贡献2%;较大的查询贡献了98%,但其大部分成本来自Index Seeks和Table Scans,而不是Parallelism。这让我觉得函数调用可能不是因为查询的迟缓而导致的,而是缺少某些表中的索引(不幸的是,我没有完全控制添加指数)。我在没有调用函数的情况下运行查询,并且表扫描和索引搜索仍然显示为高,但查询在大约8秒内完成。那么,我们回到功能......?

5 个答案:

答案 0 :(得分:3)

您可能希望更改UDF以在任何地方正确使用两部分表名,以便可以向其添加SCHEMABINDING子句。请参阅Improving query plans with the SCHEMABINDING option on T-SQL UDFs

答案 1 :(得分:1)

从应用的MSDN文章(MSDN - Apply):

“APPLY运算符允许您为查询的外表表达式返回的每一行调用表值函数。”

您的示例显示了一个分组依据。是否可以在对行进行分组而不是在特定查询中调用您的函数?这将减少函数必须调用的行数。

如果做不到这一点,我的另一个建议是通过优化查询来尽可能地在函数本身中挤出尽可能多的性能增益。每增加一毫秒就可以使它加起来。

答案 2 :(得分:1)

正如已经指出的那样,为外部查询中的每一行调用CROSS APPLY。因此,这里的关键问题是从多少行返回:

DECLARE @table (a, b, c)
INSERT @table (a, b, c)
SELECT (values)

SELECT t1.c1, t1.c2, t1.c3
FROM
    (
    SELECT a.c1
    FROM a
    JOIN b ON (join terms)
    WHERE a.c1 IN (SELECT a FROM @table)
    ) t1

这是对您的TVF进行的通话次数。如果(并且它是一个很大的if)TVF具有类似于a.c2的任何值的执行时间,那么相关的性能比较是函数的单个执行时间*从上面的查询返回的行。

由于原始查询的混淆/概括,很难确定,但我怀疑您的TVF可以被删除,并且逻辑内联到父查询。如果可行,这可能会让你获得最佳表现。

答案 3 :(得分:0)

我认为你最好的选择是在SSMS中运行它并检查你的执行计划。由于这是一个内联表值UDF,优化器会将它合并到执行计划中,您应该能够看到事情的发展方向。

我在CROSS APPLY情况下使用PIVOT子查询的经验不多 - 这让我觉得可能是一个问题。但执行计划肯定会告诉你。

答案 4 :(得分:0)

到目前为止,我已经能够将性能从~2:30提高到~0:17。它更好,但仍然不理想。我做了以下事情:

  • 为我的电视udf添加了架构绑定(谢谢你,Remus!)。这有所帮助,但似乎对性能的影响小于以下情况。

  • 重构主要查询以加入@table而不是在子查询中引用它:这似乎有助于最大化,并且大多数性能提升似乎来自。

我认为我的剩余滞后是由于大型桌子上的一些缺失指数我正在大量打击,但是没有添加它们的能力我不知道我能做什么。我已经将查询分析器报告的并行性成本降低到0%,所以我认为我已经尽力完成了函数调用。

谢谢大家!