SQL Server 2005 标量 UDF 性能

Posted

技术标签:

【中文标题】SQL Server 2005 标量 UDF 性能【英文标题】:SQL Server 2005 Scalar UDF performance 【发布时间】:2008-12-22 15:12:17 【问题描述】:

我有一个存储经纬度坐标的表,我想进行查询,以获取某个点距离内的所有记录。

这个表有大约 1000 万条记录,并且在 Lat/Long 字段上有一个索引

这不需要很精确。除其他事项外,我正在考虑 1 度长 == 1 度纬度,我知道这不是真的,但我得到的椭圆足以达到此目的。

对于下面的示例,假设有问题的点是 [40, 140],而我的半径(以度为单位)是 2 度。

我试过这两种方法:


1) 我创建了一个 UDF 来计算 2 点之间距离的平方,并且我正在查询中运行该 UDF。

SELECT Lat, Long FROM Table   
WHERE (Lat BETWEEN 38 AND 42)   
  AND (Long BETWEEN 138 AND 142)  
  AND dbo.SquareDistance(Lat, Long, 40, 140) < 4

我首先按正方形进行过滤,以加快查询速度并让 SQL 使用索引,然后对其进行细化以仅将落在圆圈内的记录与我的 UDF 匹配。


2) 运行查询以获取正方形(与之前相同,但没有最后一行),将所有这些记录提供给我的 ASP.Net 代码,并在 ASP.Net 端计算圆(同样的想法,计算保存 Sqrt 调用的距离的平方,并与我的半径的平方进行比较)。


令我惊讶的是,在 .Net 端计算圆的速度比使用 UDF 快大约 10 倍,这让我相信我在使用该 UDF 时做错了什么......

这是我正在使用的代码:

CREATE FUNCTION [dbo].[SquareDistance] 
(@Lat1 float, @Long1 float, @Lat2 float, @Long2 float)
RETURNS float
AS
BEGIN
    -- Declare the return variable here
    DECLARE @Result float
    DECLARE @LatDiff float, @LongDiff float

    SELECT @LatDiff = @Lat1 - @Lat2
    SELECT @LongDiff = @Long1 - @Long2

    SELECT @Result = (@LatDiff * @LatDiff) + (@LongDiff * @LongDiff)

    -- Return the result of the function
    RETURN @Result

END

我在这里遗漏了什么吗? 在 SQL Server 中使用 UDF 不应该比提供给 .Net 所需的记录多 25% 的记录要快得多,还有 DataReader 的开销、进程之间的通信等等?

我在那个 UDF 中做错了什么导致它运行缓慢? 有什么办法可以改善吗?

非常感谢!

【问题讨论】:

【参考方案1】:

您可以通过不声明变量并更加内联地进行计算来提高此 UDF 的性能。这可能会稍微提高性能(但可能不会太多)。

CREATE FUNCTION [dbo].[SquareDistance] 
(@Lat1 float, @Long1 float, @Lat2 float, @Long2 float)
RETURNS float
AS
BEGIN
    Return ( SELECT ((@Lat1 - @Lat2) * (@Lat1 - @Lat2)) + ((@Long1 - @Long2) * (@Long1 - @Long2)))
END

最好是删除函数并将计算放在原始查询中。

SELECT Lat, Long FROM Table   
WHERE (Lat BETWEEN 38 AND 42)   
  AND (Long BETWEEN 138 AND 142)  
  AND ((Lat - 40) * (Lat - 40)) + ((Long - 140) * (Long - 140))  < 4

调用用户定义的函数会有一点开销。通过删除该函数,您可能会获得一点性能。

另外,我鼓励您检查您的执行计划,以确保您获得预期的索引搜索。

【讨论】:

哇,我现在感觉很愚蠢,因为没有将计算从 UDF 转换为直接 SQL……我会试试这个并检查它是如何工作的。至于索引,它肯定是在使用它们,它甚至不接触表格,它是在寻找,而不是扫描。谢谢!!【参考方案2】:

在使用 UDF 时有一个 lot of overhead。

即使是内联编码也可能不好,因为不能使用索引,尽管这里的 BETWEEN 子句应该减少需要处理的数据。

为了扩展 G Mastros 的想法,将选择位与方形位分开。它可能对优化器有所帮助。

SELECT
    Lat, Long
FROM
    (
    SELECT
        Lat, Long
    FROM 
        Table   
    WHERE
        (Lat BETWEEN 38 AND 42)   
        AND
        (Long BETWEEN 138 AND 142)
    ) foo
WHERE
    ((Lat - 40) * (Lat - 40)) + ((Long - 140) * (Long - 140))  < 4

编辑:您也许可以减少所涉及的实际计算。 下一个想法可能会将计算次数从 7 减少到 5

    ...
    SELECT
        Lat, Long,
        Lat - 40 AS LatDiff, Long - 140 AS LongDiff
    FROM 
    ...
    (LatDiff * LatDiff) + (LongDiff * LongDiff)  < 4
    ...

基本上,尝试提供的 3 种解决方案,看看哪些有效。 优化器可能会忽略派生表,可能会使用它,或者可能会生成更糟糕的计划。

【讨论】:

它可能对优化器有所帮助,但可能不会。优化器足够聪明,可以识别派生表并优化查询,就好像它不存在一样。 没错,但它也有助于提高可读性。编辑为“Lat - 40”、“Long - 40”的内部查询添加更多工作,而不是外部查询的两次【参考方案3】:

查看this 文章,该文章描述了为什么 SQL Server 中的 UDF 通常来说是个坏主意。除非您非常确定您正在调用 UDF 的表不会增长很多,否则请注意 UDF 函数总是在表中的所有行上调用,而不是(正如人们可能错误地猜测的那样)仅在结果集上调用。当数据库增长时,这会给您带来很大的性能损失。

非常好的文章链接了一些解决问题的方法,但事实是 SQL Server TSQL 方言错过了创建标量函数或确定性函数的方法(就像 Oracle 所做的那样)。

【讨论】:

【参考方案4】:

更新:

GMastros:你说的完全正确。在查询本身中进行数学运算比 UDF 快得多。我正在使用 SQUARE() 函数进行乘法运算,这使它更简洁一些,但性能是相同的。

但是,这样做仍然是在 .Net 中进行数学运算的两倍。 我真的不能理解,但我已经达成了一个对我的特殊情况有用的折衷方案(这很糟糕,因为我需要复制代码,但这是最好的方案,除非我们能找到一种方法来制作圆圈SQL中的计算更快)

谢谢!

【讨论】:

以上是关于SQL Server 2005 标量 UDF 性能的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2008 R2 - 标量 UDF 导致无限循环

在 SQL Server 2005 中创建 UDF 时出错

SQL Server用户定义的函数(UDF)使用详解

用户定义的标量函数的 SQL*Server 常量值 - 性能

用东西 sql 修剪标量 UDF

使用 EXECUTE 从 SQL Server 调用用户定义函数时的标量结果不同