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 导致无限循环