为什么我的 SQL Server 空间搜索比 PostGIS 慢?

12
我正在将一些空间搜索功能从带有PostGIS的Postgres迁移到SQL Server,但即使使用索引,性能也非常糟糕。
我的数据大约有一百万个点,我想找出哪些点在给定的形状内,因此查询看起来像这样:
DECLARE @Shape GEOMETRY = ...
SELECT * FROM PointsTable WHERE Point.STWithin(@Shape) = 1

如果我选择一个相当小的形状,有时可以获得亚秒级的时间,但如果我的形状相当大(有时候确实很大),我可能需要超过5分钟的时间。如果我在Postgres中运行相同的搜索,它们总是在一秒以下(事实上,几乎所有搜索都在200毫秒以下)。
我尝试了几种不同的索引网格大小(全部高、全部中、全部低)、每个对象的单元格数(16、64、256),无论我做什么,时间都保持相当稳定。我想尝试更多的组合,但我甚至不知道该走哪个方向。每个对象的单元格数更多?更少?一些奇怪的网格大小组合?
我查看了我的查询计划,它们总是在使用索引,但完全没有帮助。我甚至尝试过没有索引,也没有太大的恶化。
有人能给出任何建议吗?我找到的所有东西都表明“我们不能就索引给你任何建议,只需尝试一切,也许会有一个有效”,但创建索引要花费10分钟,这样盲目地做是巨大的时间浪费。
编辑: 我还在Microsoft论坛上发布了这篇文章。下面是他们在那里要求的一些信息:
CREATE SPATIAL INDEX MapTesting_Location_Medium_Medium_Medium_Medium_16_NDX
    ON MapTesting (Location)
 USING GEOMETRY_GRID
  WITH (
    BOUNDING_BOX = ( -- The extent of our data, data is clustered in cities, but this is about as small as the index can be without missing thousands of points
        XMIN = -12135832,
        YMIN = 4433884,
        XMAX = -11296439,
        YMAX = 5443645),
    GRIDS = (
        LEVEL_1 = MEDIUM,
        LEVEL_2 = MEDIUM,
        LEVEL_3 = MEDIUM,
        LEVEL_4 = MEDIUM),
     CELLS_PER_OBJECT = 256 -- This was set to 16 but it was much slower
  )

我在使用索引时遇到了一些问题,但这与现在的情况不同。

对于这些测试,我针对我的每个索引(测试不同网格大小和每个对象的单元格设置)运行了一个带有WITH(INDEX(...))子句的测试搜索(列在我的原始帖子中),以及一个没有任何提示的搜索。我还使用每个索引和相同的搜索形状运行了sp_help_spatial_geometry_index。上述索引运行速度最快,并且在sp_help_spatial_geometry_index中被列为最有效。

运行搜索时,我得到了以下统计数据:

(1 row(s) affected)
Table 'MapTesting'. Scan count 0, logical reads 361142, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'extended_index_592590491_384009'. Scan count 1827, logical reads 8041, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 6735 ms,  elapsed time = 13499 ms.

我也尝试使用随机数据点(因为我不能提供我们真实的数据),但结果表明,这个搜索对于随机数据非常快。这使我们相信,我们的问题在于网格系统如何处理我们的数据。
我们的数据跨越整个州的地址,因此有一些非常高密度的区域,但大多数是稀疏的数据。我认为问题在于没有一种网格大小的设置适用于两者。将网格设置为“HIGH”时,在低密度区域返回过多的单元格,而将网格设置为“LOW”时,在高密度区域中网格无用(在“MEDIUM”下,情况没有那么糟糕,但仍然不太好)。 我能够获得使用的索引,但它没有帮助。每个测试都打开了“显示实际执行计划”,并且始终显示索引。

+1:我也想知道。 - Nordic Mainframe
你的百万数据点是以几何(平面欧几里得空间)还是地理(圆形、地球形坐标)的方式存储的?如果你混合使用几何和地理,那么会引入转换计算的性能损失。 - mwalker
@Mwalker,这都是几何问题,我觉得你无论如何都不能混合使用它们。 - Brendan Long
感谢关于CELLS_PER_OBJECT的评论。我使用CELLS_PER_OBJECT = 16和levels = MEDIUM时,需要6.8秒,但是当我将CELLS_PER_OBJECT更改为256并将levels更改为HIGH时,时间降至2.8秒 :-) - Guilherme Campos Hazan
8个回答

5
我刚刚花了一整天来解决一个类似的问题。具体而言,我们正在进行点与多边形类型查询,其中有一组相对较小的多边形,但每个多边形都很大且复杂。
解决方案如下,针对多边形表上的空间索引:
  1. 使用“geometry auto grid”而不是旧的MMLL等。这样可以获得8个索引级别,而不是旧的4个,并且设置是自动的。并且...
  2. 将“每个对象的单元格数”设置为2000或4000。(默认值为16,不容易猜测)
这使差异巨大,比默认配置下的空间索引快10倍,比没有索引快60倍。

3
以下是关于SQL-Server空间扩展的一些注释以及如何确保索引被有效使用的方法: 显然,如果在解析期间不知道实际几何图形,规划器很难构建一个好的计划。作者建议插入exec sp_executesql
将以下内容替换:
-- does not use the spatial index without a hint
declare @latlonPoint geometry = geometry::Parse('POINT (45.518066 -122.767464)')
select a.id, a.shape.STAsText() 
from zipcodes a 
where a.shape.STIntersects(@latlonPoint)=1
go

使用:

-- this does use the spatial index without using a hint
declare @latlonPoint geometry = geometry::Parse('POINT (45.518066 -122.767464)')
exec sp_executesql 
N'select a.id, a.shape.STAsText() 
from zipcodes a 
where a.shape.STIntersects(@latlonPoint)=1', N'@latlonPoint geometry', @latlonPoint
go

我的空间索引正在使用中。我点击了“包括实际执行计划”,它显示正在使用空间索引。 - Brendan Long
我只是为了确保尝试了这个建议,时间和执行计划都是相同的。 - Brendan Long

2

不考虑实现效率问题,SQL Server使用Quadtree index,而PostGIS使用R-tree索引。

R树算法通常更好,特别是对于具有不同几何尺寸的大型数据集。


2

我认为STIntersects对于使用索引进行优化会比STWithin更具有性能优势,特别是对于较大的形状。


在执行STIntersects查询时,您是否可以在设置set statistics profile on之后发布计划信息? - Giri

2
我的第一反应是“因为微软没有费心让它变快,因为它不是企业功能”。也许我有点愤世嫉俗。
我也不确定你为什么要迁移到Postgres之外。

我怀疑这更多是因为它是一个新功能;听说下一个版本会让它变得更好。让我困惑的是,我没有听说过它运行缓慢的问题,所以我担心我可能漏掉了什么。 - Brendan Long
1
在使用SQL Server中的空间索引后,我开始认为这是真实的。 - Paul Mendoza

1

你可以尝试分成两个步骤:

  1. 使用 .Filter() 将候选项选择到一个临时表中。
  2. 使用 .STWithin() 查询候选项。

例如:

SELECT * INTO #this FROM PointsTable WHERE Point.Filter(@Shape) = 1
SELECT * FROM #this WHERE Point.STWithin(@Shape) = 1

(用实际需要的列替换SELECT *以减少I/O)

这种微观优化本来不应该是必要的,但我之前确实看到过相当不错的性能提升。此外,通过比较(1)和(2)的比率,您还可以评估索引的选择性。


1

你是否正确设置了空间索引?你的边界框是否正确?所有点都在里面吗?在你的情况下,GRIDS可能是最好的选择(再次取决于边界框)。

你可以尝试使用sp_help_spatial_geometry_index来查看问题所在。 http://msdn.microsoft.com/en-us/library/cc627426.aspx

尝试使用Filter操作,告诉我们你得到了什么性能数字?(它只执行主要过滤器(使用索引),而不经过二级过滤器(真正的空间操作))

你的设置有问题。空间确实是一个新功能,但并不那么糟糕。


我尝试了每种两个大小的组合(LLLL,LLMM,LLHH,MMLL等),最好的是每个对象256个单元的MMMM。 sp_help_spatial_geometry_index 表示主过滤器的效率为90%,我认为这可能是问题所在(其他过滤器低至70%)。 FilterSTIntersects 快得多,但仍比Postgres慢2-5倍(并且不够准确)。 - Brendan Long
我们认为问题在于我们的数据相对稀疏,但有高密度区域,因此静态网格大小方法并不有用。如果我们将网格设置得太高,在稀疏区域中索引过于具体,但如果我们将其设置得太低,则在高密度区域中索引无用。 - Brendan Long
然后尝试在每个高密度区域周围设置多个空间索引。或者至少将整个美国分成几个大区域。我预计您的大部分数据都集中在东海岸和西海岸。 - Desinderlase
我们的数据不是整个美国,只是科罗拉多州。问题在于用户可以选择跨越整个州的区域。例如,我的测试查询是从福特·柯林斯到丹佛的一个狭长的框。这有两个高密度区域和一个低密度区域分隔开来,我认为SQL Server一次只能使用一个空间索引(而不索引这两个区域中的任何一个将比现在更糟)。 - Brendan Long
我可以在周一再次尝试加载这个数据库进行测试,目前我们只是将其删除了,因为这个配置比使用两个数据库要复杂得多。 - Brendan Long
SQL Server存在一个问题,即相对较粗糙的分辨率——即使是覆盖整个世界的HHHH网格也会出现每边超过600米的正方形。 - Andrew Hill

0

我不熟悉空间查询,但这可能是参数化查询的问题。

尝试编写一个查询(不使用参数)并使用固定值(使用在参数化查询中执行缓慢的值),然后运行它。将其与参数化版本的时间进行比较。如果速度快得多,则您的问题就是参数化查询。

如果上述方法更快,则我建议动态构建带有嵌入在字符串中的参数值的SQL字符串,这样您就可以消除参数引起的问题。


看起来查询参数化不太可能是问题所在。OP说:“如果我选择一个相当小的形状,有时可以获得小于一秒的时间,但如果我的形状相当大(有时候确实很大),我可以获得超过5分钟的时间。” 1. 查询性能似乎受到查询几何空间属性的影响。2. 即使您认为参数化查询可能很慢,它们也不应该将快速查询转变为需要超过5分钟才能执行的查询...我想不出任何合理的解释为什么它们会产生这种效果。 - stakx - no longer contributing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接