内联表值函数的性能

3

我正在使用SQL Server 2008R2。我编写了以下表值函数UDF,它接受标量值this或that或两者作为参数,并返回带有ID、this和that列的表。当我从复杂查询中调用该函数时,性能非常差,但在简单查询中调用时则没有问题。我想知道是否有人对我做的事情有任何想法,这会减慢速度。函数定义如下:

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%,但它的大部分成本来自索引查找和表扫描,而不是来自并行处理。这使我想到,也许函数调用并不是查询运行缓慢的原因,而是涉及某些表的缺乏索引(不幸的是,我对添加索引没有太多控制)。我运行了没有调用函数的查询,表扫描和索引查找仍然很高,但查询完成约需8秒。所以,我们又回到了函数的问题...?
5个回答

3

1

来自 MSDN 的 Apply 文章(MSDN - Apply):

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

你的例子展示了一个分组。是否可能在行被分组之后调用你的函数,而不是在那个特定的查询中调用它?这将减少函数需要调用的行数。

如果无法实现上述方法,我的另一个建议是通过优化函数本身中的查询来尽可能地提高性能增益。每毫秒更快都会累加。


谢谢您的建议:我不确定在应用函数之前是否可以进行分组,因为它的作用类似于“翻译器”,将这个转换为那个,反之亦然。我的想法是按ID分组计算这个和那个的数量:据我所知,如果我先进行分组,我将尝试将整数转换为这个或那个,这是行不通的。 - AnnStimmel

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 的次数。如果(这是一个大的假设)TVF 对于任何值的 a.c2 具有类似的执行时间,则相关性能比较是函数的单个执行时间 * 上面查询返回的行数。

由于原始查询的混淆/概括,很难确定,但我怀疑您的 TVF 可以被消除,并且逻辑可以内联到父查询中。如果可行,这可能会为您带来最佳性能。


谢谢,马克!你对我的概括是正确的:我担心我把它简化得太多了。你引用的伪代码选择语句是其中之一,每个语句都调用该函数。如果我要消除该函数并应用逻辑内联,则必须在此查询中执行4次(以及我们整个系统中的更多次)。我看到我正在为交叉应用的每一行调用该函数,但认为它相对廉价,因为它是单语句。 - AnnStimmel

0

我认为你最好的选择是在SSMS中运行它,并检查你的执行计划。由于这是一个内联表值UDF,优化器将把它纳入执行计划中,你应该能够看到哪些地方出了问题。

我没有在CROSS APPLY情况下使用PIVOT子查询的经验 - 这可能是一个问题。但执行计划会告诉你确切的情况。


0

到目前为止,我已经成功将性能从大约2分30秒提高到了大约0分17秒。这是一个进步,但仍然不理想。我做了以下几点:

  • 在我的电视UDF中添加了模式绑定(谢谢Remus!)。这有所帮助,但似乎对性能的影响比以下操作要小。

  • 重构主查询以加入@table而不是在子查询中引用它:这似乎是最有帮助的,并且是大部分性能提升的来源。

我认为我剩下的滞后是由于我正在频繁访问的大型表上缺少一些索引,但是由于无法添加它们,我不确定我现在能做什么。我已将并行成本降低到了查询分析器报告的0%,因此我认为我已经尽了调用函数的所有可能性。

谢谢大家!


1
注意,QA/SSMS中的执行计划百分比是估算值,而不是实际成本。特别是并行处理可能会严重扭曲计划中的估算与实际成本之间的差异。另外,作为SO的新手...您发现有用的任何答案都应该被“upvoted”,您认为是解决问题的答案应该被标记为这样。 - Mark Storey-Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接