为什么UDF比子查询慢得多?

时间:2009-02-04 10:01:00

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

我有一个案例需要从同一个表中翻译(查找)多个值。我编写它的第一种方法是使用子查询:

SELECT
    (SELECT id FROM user WHERE user_pk = created_by) AS creator,
    (SELECT id FROM user WHERE user_pk = updated_by) AS updater,
    (SELECT id FROM user WHERE user_pk = owned_by) AS owner,
    [name]
FROM asset

因为我经常使用这个子查询(也就是说,我有大约50个带有这些字段的表),我可能需要在子查询中添加更多代码(例如,“AND active = 1”)I以为我会将这些放入用户定义的函数UDF并使用它。但使用该UDF的性能非常糟糕。

CREATE FUNCTION dbo.get_user ( @user_pk INT )
RETURNS INT
AS BEGIN 
    RETURN ( SELECT id
             FROM   ice.dbo.[user]
             WHERE  user_pk = @user_pk )
END

SELECT dbo.get_user(created_by) as creator, [name]
FROM asset

#1的表现不到1秒。 #2的表现大约是30秒......

为什么,或者更重要的是,我有什么方法可以在SQL Server 2008中编码,这样我就不必使用这么多子查询了?

编辑:

只是对这个有用时的更多解释。当我想为用户提供文本时,这个简单的查询(即获取用户ID)会变得更加复杂,因为我必须加入配置文件来获取语言,并与公司一起查看该语言是否应该被提取'而是从那里编辑,并与翻译表,以获得翻译的文本。对于大多数这些查询,性能是可读性和可维护性的次要问题。

4 个答案:

答案 0 :(得分:32)

UDF是查询优化器的黑盒子,因此它针对每一行执行。 你正在做一个逐行的游标。对于资产中的每一行,在另一个表中查找三次id。当您使用标量或多语句UDF时会发生这种情况(内联UDF只是扩展到外部查询中的宏)

关于这个问题的许多文章之一是“Scalar functions, inlining, and performance: An entertaining title for a boring post”。

可以优化子查询以关联并避免逐行操作。

你真正想要的是:

select 
   uc.id as creator,
   uu.id as updater,
   uo.id as owner,
   a.[name]
from
    asset a
    JOIN
    user uc ON uc.user_pk = a.created_by
    JOIN
    user uu ON uu.user_pk = a.updated_by
    JOIN
    user uo ON uo.user_pk = a.owned_by

2019年2月更新

SQL Server 2019开始解决此问题。

答案 1 :(得分:12)

正如其他海报所建议的那样,使用连接肯定会给你最好的整体表现。

但是,既然您已经声明不想要保持50-ish类似连接或子查询的头痛,请尝试使用内联表值函数,如下所示:

CREATE FUNCTION dbo.get_user_inline (@user_pk INT)
RETURNS TABLE AS
RETURN
(
    SELECT TOP 1 id
    FROM ice.dbo.[user]
    WHERE user_pk = @user_pk
        -- AND active = 1
)

您的原始查询将变为类似:

SELECT
    (SELECT TOP 1 id FROM dbo.get_user_inline(created_by)) AS creator,
    (SELECT TOP 1 id FROM dbo.get_user_inline(updated_by)) AS updater,
    (SELECT TOP 1 id FROM dbo.get_user_inline(owned_by)) AS owner,
    [name]
FROM asset

inline table-valued function应该比标量函数或多语句表值函数具有更好的性能。

性能应该与原始查询大致相同,但是可以在UDF中进行任何未来的更改,使其更易于维护。

答案 2 :(得分:2)

获得相同的结果(如果用户被删除或未激活,则为NULL)。

 select 
    u1.id as creator,
    u2.id as updater,
    u3.id as owner,
    [a.name]
 FROM asset a
        LEFT JOIN user u1 ON (u1.user_pk = a.created_by AND u1.active=1) 
        LEFT JOIN user u2 ON (u2.user_pk = a.created_by AND u2.active=1) 
        LEFT JOIN user u3 ON (u3.user_pk = a.created_by AND u3.active=1) 

答案 3 :(得分:0)

我错过了什么吗?为什么这不行?您只是选择表中已有的ID:

select created_by as creator, updated_by as updater, 
owned_by as owner, [name]
from asset

顺便说一下,在设计时,你真的应该避免像name这样的关键字作为字段名称。