SQL Server 2005标量UDF性能

时间:2008-12-22 15:12:17

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

我有一个表格,我正在存储Lat / Long坐标,我想查询一下我想要获取距离某一点的所有记录。

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

这不需要精确。除此之外,我正在考虑1度长== 1度Lat,我知道这不是真的,但我得到的椭圆对于这个目的来说足够好了。

对于我下面的例子,假设有问题的点是[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中是否存在使其运行缓慢的可怕错误? 有没有办法改善它?

非常感谢!

4 个答案:

答案 0 :(得分:3)

您可以通过NOT声明变量并更直接地进行计算来提高此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

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

另外,我建议您检查一下执行计划,以确保获得预期的索引。

答案 1 :(得分:3)

使用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个解决方案,看看哪些有效。 优化器可能会忽略派生表,它可能会使用它,或者它可能会生成更糟糕的计划。

答案 2 :(得分:1)

查看this文章,该文章描述了为什么SQL Server中的UDF通常是个坏主意。除非你非常确定你调用UDF的表不会长大,所以要注意UDF函数总是在表中的所有行上调用,而不是(只能错误地猜测)在结果集上。这可以在数据库增长时给您带来巨大的性能损失。

非常好的文章链接细节也有一些方法可以克服这个问题,但事实上,SQL Server TSQL方言错过了创建标量函数或确定函数的方法(就像Oracle一样)。

答案 3 :(得分:0)

更新

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

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

谢谢!