1

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

该表有大约 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 中做错了什么导致它运行缓慢?
有什么办法可以改善吗?

非常感谢你!

4

4 回答 4

3

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

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

另外,我鼓励你检查你的执行计划,以确保你得到你期望的索引搜索。

于 2008-12-22T15:28:59.460 回答
3

使用 UDF有很多开销

即使是内联编码也可能不好,因为不能使用索引,尽管这里的 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 种解决方案,看看哪些有效。优化器可能会忽略派生表,它可能会使用它,或者它可能会生成更糟糕的计划。

于 2008-12-22T15:58:04.537 回答
1

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

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

于 2009-01-26T17:31:22.480 回答
0

更新:

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

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

谢谢!

于 2008-12-22T18:02:22.943 回答