选择一个适合大型多边形的好的SQL Server 2008空间索引

12

我正在尝试为我处理的数据集选择一个合适的SQL Server 2008空间索引设置,感觉很有趣。

该数据集是表示全球轮廓线的多边形。表中有106000行,多边形存储在几何字段中。

我的问题是,许多多边形涵盖了地球的大部分区域。这似乎使得很难获得一个能够在主要过滤器中消除许多行的空间索引。例如,请看以下查询:

SELECT "ID","CODE","geom".STAsBinary() as "geom" FROM "dbo"."ContA"
WHERE "geom".Filter(
  geometry::STGeomFromText('POLYGON ((-142.03193662573682 59.53396984952896,
    -142.03193662573682 59.88928136451884,
    -141.32743833481925 59.88928136451884,
    -141.32743833481925 59.53396984952896,
    -142.03193662573682 59.53396984952896))', 4326)
) = 1
这是查询与表中仅两个多边形相交的区域。无论我选择哪种空间索引设置,Filter()始终返回约60,000行数据。用STIntersects()替换Filter()当然只返回我想要的两个多边形,但需要更长时间(Filter()为6秒,STIntersects()为12秒)。是否有空间索引设置可以改善60,000行数据,或者我的数据集不适合SQL Server的空间索引?更多信息:如建议的那样,我使用全球4x4网格将多边形分割开来。我找不到使用QGIS的方法,所以我编写了自己的查询。首先,我定义了16个边界框,第一个看起来像这样:
declare  @box1 geometry = geometry::STGeomFromText('POLYGON ((
-180 90,
-90 90,
-90 45,
-180 45,
-180 90))', 4326)

然后我使用每个边界框来选择和截断与该框相交的多边形:

insert ContASplit
select CODE, geom.STIntersection(@box1), CODE_DESC from ContA
where geom.STIntersects(@box1) = 1

我显然对4x4网格中的所有16个边界框都这样做了。最终结果是我有了一个新表,大约有107,000行(这证实了我实际上没有很多巨大的多边形)。

我添加了一个空间索引,每个对象有1024个单元格,并且每个级别的单元格数量都非常低。

然而,非常奇怪的是,这个拆分多边形的新表的性能仍然与旧表相同。进行上面列出的.Filter()仍然返回大约60,000行。我真的完全不理解这一点,显然我不理解空间索引的工作原理。

矛盾的是,虽然.Filter()仍然返回大约60,000行,但它的性能已经得到了改善。.Filter()现在需要大约2秒钟,而不是6秒钟,.STIntersects()现在需要6秒钟,而不是12秒钟。

按要求,这里是索引的SQL示例:

CREATE SPATIAL INDEX [contasplit_sidx] ON [dbo].[ContASplit] 
(
    [geom]
)USING  GEOMETRY_GRID 
WITH (
BOUNDING_BOX =(-90, -180, 90, 180),
GRIDS =(LEVEL_1 = LOW,LEVEL_2 = LOW,LEVEL_3 = LOW,LEVEL_4 = LOW), 
CELLS_PER_OBJECT = 1024,
PAD_INDEX  = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ALLOW_ROW_LOCKS  = ON,
ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

请记住,我已经尝试了各种网格和每个对象的单元格设置,但每次结果都相同。

这是运行sp_help_spatial_geometry_index的结果,在我的拆分数据集上,没有任何单个多边形占据了超过1/16的地球表面:

Base_Table_Rows 215138 Bounding_Box_xmin -90 Bounding_Box_ymin -180 Bounding_Box_xmax 90 Bounding_Box_ymax 180 Grid_Size_Level_1 64 Grid_Size_Level_2 64 Grid_Size_Level_3 64 Grid_Size_Level_4 64 Cells_Per_Object 16 Total_Primary_Index_Rows 378650 Total_Primary_Index_Pages 1129 Average_Number_Of_Index_Rows_Per_Base_Row 1 Total_Number_Of_ObjectCells_In_Level0_For_QuerySample 1 Total_Number_Of_ObjectCells_In_Level0_In_Index 60956 Total_Number_Of_ObjectCells_In_Level1_In_Index 361 Total_Number_Of_ObjectCells_In_Level2_In_Index 2935 Total_Number_Of_ObjectCells_In_Level3_In_Index 32420 Total_Number_Of_ObjectCells_In_Level4_In_Index 281978 Total_Number_Of_Interior_ObjectCells_In_Level2_In_Index 1 Total_Number_Of_Interior_ObjectCells_In_Level3_In_Index 49 Total_Number_Of_Interior_ObjectCells_In_Level4_In_Index 4236 Total_Number_Of_Intersecting_ObjectCells_In_Level1_In_Index 29 Total_Number_Of_Intersecting_ObjectCells_In_Level2_In_Index 1294 Total_Number_Of_Intersecting_ObjectCells_In_Level3_In_Index 29680 Total_Number_Of_Intersecting_ObjectCells_In_Level4_In_Index 251517 Total_Number_Of_Border_ObjectCells_In_Level0_For_QuerySample 1 Total_Number_Of_Border_ObjectCells_In_Level0_In_Index 60956 Total_Number_Of_Border_ObjectCells_In_Level1_In_Index 332 Total_Number_Of_Border_ObjectCells_In_Level2_In_Index 1640 Total_Number_Of_Border_ObjectCells_In_Level3_In_Index 2691 Total_Number_Of_Border_ObjectCells_In_Level4_In_Index 26225 Interior_To_Total_Cells_Normalized_To_Leaf_Grid_Percentage 0.004852925 Intersecting_To_Total_Cells_Normalized_To_Leaf_Grid_Percentage 0.288147586 Border_To_Total_Cells_Normalized_To_Leaf_Grid_Percentage 99.70699949 Average_Cells_Per_Object_Normalized_To_Leaf_Grid 405.7282349 Average_Objects_PerLeaf_GridCell 0.002464704 Number_Of_SRIDs_Found 1 Width_Of_Cell_In_Level1 2.8125 Width_Of_Cell_In_Level2 0.043945313 Width_Of_Cell_In_Level3 0.000686646 Width_Of_Cell_In_Level4 1.07E-05 Height_Of_Cell_In_Level1 5.625 Height_Of_Cell_In_Level2 0.087890625 Height_Of_Cell_In_Level3 0.001373291 Height_Of_Cell_In_Level4 2.15E-05 Area_Of_Cell_In_Level1 1012.5 Area_Of_Cell_In_Level2 15.8203125 Area_Of_Cell_In_Level3 0.247192383 Area_Of_Cell_In_Level4 0.003862381 CellArea_To_BoundingBoxArea_Percentage_In_Level1 1.5625 CellArea_To_BoundingBoxArea_Percentage_In_Level2 0.024414063 CellArea_To_BoundingBoxArea_Percentage_In_Level3 0.00038147 CellArea_To_BoundingBoxArea_Percentage_In_Level4 5.96E-06 Number_Of_Rows_Selected_By_Primary_Filter 60956 Number_Of_Rows_Selected_By_Internal_Filter 0 Number_Of_Times_Secondary_Filter_Is_Called 60956 Number_Of_Rows_Output 2 Percentage_Of_Rows_NotSelected_By_Primary_Filter 71.66655821 Percentage_Of_Primary_Filter_Rows_Selected_By_Internal_Filter 0 Internal_Filter_Efficiency 0 Primary_Filter_Efficiency 0.003281055

"Base_Table_Rows 215138"这个信息对我来说并没有太多意义,因为表格里只有10.7万行,而不是21.5万行。

当渲染数据集时,它看起来像这样:alt text
(来源:norman.cx

进一步调查:

我仍然对此数据的主要过滤器性能表现感到困惑。因此,我进行了一项测试,以确定我的数据如何分割。使用原始未分割的特征,我向表格中添加了一个“单元格”列。我随后运行了16个查询,以计算每个特征跨越的4x4网格中有多少个单元格。所以,我为每个单元格运行了如下查询:

declare  @box1 geometry = geometry::STGeomFromText('POLYGON ((
-180 90,
-90 90,
-90 45,
-180 45,
-180 90))', 4326)
update ContA set cells = cells + 1 where
geom.STIntersects(@box1) = 1

如果我查看表格中的“单元格”列,整个数据集中只有672个特征与4x4网格内超过1个单元格相交。那么在查看一个仅覆盖200英里宽矩形的查询时,这个主要过滤器怎么可能会返回60,000个特征呢?

此时,看起来我可以编写自己的索引方案,使其比SQL Server为这些特征执行的方案更加有效。


空间索引在查询中一定被使用了吗?http://blogs.msdn.com/b/isaac/archive/2008/08/29/is-my-spatial-index-being-used.aspx - geographika
是的,空间索引肯定被使用了。 - andynormancx
很不幸,在我使用数据集的情况下,将整个瓦片缓存渲染到用户可以缩放的级别是不切实际的。 - andynormancx
如果您使用过滤器而不是交集,您的新查询会有什么结果? 您的数据集分辨率是多少?您只需要创建一个与原始数据精度相等的光栅图像。如果等高线数据集的精度为5公里,则无需创建代表1米土地的像素。 - geographika
希望我更加关注 Bounding_Box_xmin -90 Bounding_Box_ymin -180 Bounding_Box_xmax 90 Bounding_Box_ymax 180! - geographika
显示剩余9条评论
3个回答

12

在你的索引查询中,你使用了:

CREATE SPATIAL INDEX [contasplit_sidx] ON [dbo].[ContASplit] 
(
    [geom]
)USING  GEOMETRY_GRID 
WITH (
BOUNDING_BOX =(-90, -180, 90, 180),
...
< p >BOUNDING_BOX因此映射为:

xmin = -90
ymin = -180
xmax = 90
ymax = 180
  • 经度(-180到180,表示经过本初子午线以东或以西)应映射到X轴
  • 纬度(-90到90,表示距离赤道多远向北或向南)应映射到Y轴

因此,要创建全球的BOUNDING_BOX,您应该使用:

CREATE SPATIAL INDEX [contasplit_sidx] ON [dbo].[ContASplit] 
(
    [geom]
)USING  GEOMETRY_GRID 
WITH (
BOUNDING_BOX =(-180, -90, 180, 90),
...

这应该创建一个适合您的数据并意味着索引覆盖了所有特征的索引。


谢谢,谢谢,谢谢。看起来我用来导入形状文件的工具最初创建了错误的边界框索引,自那以后我一直在机械地复制同样的错误。 - andynormancx

5

数据分割

如果查询是用于显示数据,则可以使用网格将大型多边形拆分。这些多边形将非常快速地通过索引检索。您可以删除轮廓线,使要素仍然看起来连续。

大多数商业GIS软件都会有工具来通过另一个数据集来分割一个多边形。搜索执行交集操作的工具。

如果您正在使用开源软件,请查看QGIS和http://www.ftools.ca,它们可以“执行包括交集、差异、联合、溶解和裁剪在内的地理处理操作”。我自己没有使用过后者。

请参阅:http://postgis.refractions.net/docs/ch04.html#id2790790 了解为什么大型要素很糟糕。

筛选和交集

这里有更多关于Filter子句的信息 - Link

空间索引

还有一件事要检查,那就是查询计划中实际使用了空间索引。您可能需要使用WITH子句强制查询使用索引:

链接

有关索引的更多详细信息如下:

链接

同时尝试运行sp_help_spatial_geometry_index以查看您的数据应该使用哪些设置来进行空间索引。

http://msdn.microsoft.com/en-us/library/cc627426.aspx

运行此存储过程并使用一些测试几何图形会生成各种统计数据,以尝试根据您的数据定制索引。完整的属性列表在http://msdn.microsoft.com/en-us/library/cc627425.aspx

这些值包括:

  • CellArea_To_BoundingBoxArea_Percentage_In_Level1
  • Number_Of_Rows_Selected_By_Primary_Filter

混乱的几何图形

从sp_help_spatial_geometry_index的结果来看,您可能存在几何图形本身而不是空间索引的问题。

Base_Table_Rows计数似乎是一个错误- http://connect.microsoft.com/SQLServer/feedback/details/475838/number-of-rows-in-base-table-incorrect-in-sp-help-spatial-geography-index-xml 也许重新创建表/数据库并从头开始尝试索引会更好。

在索引60956中,Level0中的ObjectCells总数很多,这些特性可能位于空间索引范围之外或为空值。然后对所有这些特性运行Intersect(Number_Of_Times_Secondary_Filter_Is_Called 60956),这就解释了为什么速度很慢。尽管文档声称空值特性不会影响性能 - 我认为它仍然必须查找记录,即使没有执行交集操作。

NULL和空实例在Level0中计入总数,但不会影响性能。 Level0将具有与基表中NULL和空实例一样多的单元格。

我认为Primary_Filter_Efficiency为0.003281055表示0.03%的效率!

可以尝试以下几件事:

  1. 从SELECT * FROM sys.spatial_indexes中有什么奇怪的东西吗?

  2. MakeValid语句:

    UPDATE MyTable SET GeomFieldName = GeomFieldName.MakeValid()

  3. 重置/再次检查SRID:

    UPDATE MyTable SET GeomFieldName.STSrid = 4326

  4. 添加一些字段以显示要素的范围。这可能会突出显示问题/空几何体。

    ALTER TABLE MyTable ADD MinX AS (CONVERT(int,GeomFieldName.STEnvelope().STPointN((1)).STX,0)) PERSISTED ALTER TABLE MyTable ADD MinY AS (CONVERT(int,GeomFieldName.STEnvelope().STPointN((1)).STY,0)) PERSISTED ALTER TABLE MyTable ADD MaxX AS (CONVERT(int,GeomFieldName.STEnvelope().STPointN((3)).STX,0)) PERSISTED ALTER TABLE MyTable ADD MaxY AS (CONVERT(int,GeomFieldName.STEnvelope().STPointN((3)).STY,0)) PERSISTED


我仍然困惑于为什么过滤器会返回如此多的记录。数据集中有许多大型多边形,但是SQL索引工作方式的描述应该只返回几千个记录,而不是60,000多个(整个数据集的60%)。 - andynormancx
然而,我仍然不明白为什么索引没有从筛选器中减少结果。如果我选择顶层网格中单元格数量为“低”,则网格中将有16个单元格。我的数据检查表明,只有23个多边形应出现在其中超过两个单元格中。我的查询目标只会出现在一个单元格中。那么为什么筛选器返回60,000行? - andynormancx
在sys.spatial_indexes中没有看到任何奇怪的东西。MakeValid已经用于这些数据。我检查了Srids,它们都是正确的,我为了保险起见重置了它们。我使用了你提供的额外字段来检查几何图形,它们中没有一个为空,并且它们都适合我的索引范围内。 - andynormancx
最后一件事 - 你尝试过使用地理类型吗?由于你正在使用4326和全球数据,它可能更适合。只需确保没有要素穿越赤道(你可能需要再次拆分)。 - geographika
我刚试图使用地理数据。不幸的是,我的几行几何数据中有一行或多行(可能很多)不符合地理数据的有效性要求。我还没有查看它们违反了哪些地理数据规则,这需要花费大量精力。此外,我认为GeoServer SQL Server插件尚不支持地理数据类型。由于我使用4x4网格进行分割,因此我的所有特征都没有跨越赤道。 - andynormancx
显示剩余11条评论

0

我也发现对于特定的几何表,很难“猜测”出适当的空间索引。我尝试使用sp_help_spatial_geometry_index存储过程进行更有根据的猜测,但结果只是告诉我每次“猜测”后我的空间索引性能有多差。即使我只考虑2-8 CELLS_PER_OBJECT的选项,这就有567种排列组合(3种类型选择4次=81,然后乘以7个CELLS_PER_OBJECT选项)。我决定让SQL服务器为我进行实验并给我一些经验证据。我创建了一个存储过程,它会在每个空间表上重建空间索引,并测试每个空间索引的查询性能,使用两个提供的几何实例。我选择了一个包含整个数据集的几何实例,然后选择另一个包含数据集的较小部分的实例。该过程对每个实例使用STIntersect() 4次,然后将结果记录在表中。然后您可以查询结果表,找出哪个空间索引在您特定的数据集上表现最佳。试试看,如果您有任何建议或观察,请告诉我。

使用此https://gist.github.com/anonymous/5322650创建存储过程。然后使用以下示例设置执行语句:

/* set up some strings to be used to create geometry instances when our test spatial queries run */ 
DECLARE @ada VARCHAR(MAX) 
SET @ada = 'GEOMETRY::STGeomFromText(''POLYGON ((2422068 527322, 2422068 781170, 2565405 781170, 2565405 527322, 2422068 527322))'', 0)'
DECLARE @mer VARCHAR(MAX) 
SET @mer = 'GEOMETRY::STGeomFromText(''POLYGON ((2451235 696087, 2451235 721632, 2473697 721632, 2473697 696087, 2451235 696087))'', 0)'
DECLARE @mer1 VARCHAR(MAX) 
SET @mer1 = 'GEOMETRY::STGeomFromText(''POLYGON ((244386 712283, 2443866 717980, 2454872 717980, 2454872 712283, 244386 712283))'', 0)'
DECLARE @mer2 VARCHAR(MAX) 
SET @mer2 = 'GEOMETRY::STGeomFromText(''POLYGON ((2434259 687278, 2434259 701994, 2449657 701994, 2449657 687278, 2434259 687278))'', 0)'


EXEC gis.sp_tune_spatial_index 'PARCEL_ADA', 'S104_idx', 2, 8, @ada, @mer1 
GO

注意:显然,重建空间索引567次将需要很长时间。可以使用命令行启动它或者在您做其他事情的同时让它运行。如果这是一个您经常使用的数据集且几何形状保持不变,那么运行该程序需要的时间将是值得的。结果表显示了以毫秒为单位的性能。


谢谢你的回答!与其使用被许多雇主屏蔽的Dropbox,是否可以在这里提供存储过程的要点?或者,您可以考虑使用Github Gist(https://gist.github.com/)作为替代托管位置。 - Peter Mularien
感谢您的建议。我刚刚更新了指向该网站的超链接。 - Geobility
@GreenGeo 太棒了!我修改了你的存储过程,增加了一些输出:https://geonet.esri.com/blogs/HackingArcSDE/2015/07/07/witch-magic-snake-oil-medicine-and-spatial-index-tuning - tpcolson

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