【问题标题】:Selecting a good SQL Server 2008 spatial index with large polygons选择具有大多边形的良好 SQL Server 2008 空间索引
【发布时间】:2011-02-24 15:27:23
【问题描述】:

我正在尝试为我正在处理的数据集选择一个体面的 SQL Server 2008 空间索引设置。

数据集是多边形,代表整个地球的轮廓。表中有 106,000 行,多边形存储在几何字段中。

我遇到的问题是许多多边形覆盖了地球的很大一部分。这似乎很难获得将消除主过滤器中的许多行的空间索引。例如,查看以下查询:

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 个单元格,每个级别的单元格为 low,low,low,low。

但是,非常奇怪的是,这个带有分割多边形的新表仍然与旧表一样。执行上面列出的 .Filter still 会返回约 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”对我来说没有多大意义,表中有 107,000 行,而不是 215,000 行

渲染后的数据集如下所示:
(来源: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 网格中的多个单元格相交。那么,从字面上看,主过滤器究竟如何才能为查看一个 200 英里宽的小矩形的查询返回 60,000 个特征?

在这一点上,我似乎可以编写自己的索引方案,它会比 SQL Server 在这些功能上的表现更好。

【问题讨论】:

  • 空间索引确定在查询中使用吗? blogs.msdn.com/b/isaac/archive/2008/08/29/…
  • 是的,空间索引肯定在使用。
  • 听起来您的多边形尺寸范围很广。如果您只需要显示数据,请考虑一次性创建切片缓存,或将数据转换为栅格数据集。
  • 不幸的是,在我使用数据集的情况下,将整个切片缓存渲染到用户能够放大到的级别是不切实际的。
  • 我建议与 SQL Server 2008 Spatial 的开发人员联系。他们经常在地理空间博客上发帖,似乎对 cme​​ts 等开放。试试blogs.msdn.com/b/isaac/archive/2008/03/01/…的“电子邮件博客作者”按钮

标签: sql-server-2008 geospatial spatial spatial-query spatial-index


【解决方案1】:

拆分数据

如果查询是为了显示数据,那么您可以使用网格分割大多边形。这些将很快通过索引检索。您可以删除轮廓,使特征看起来仍然是连续的。

大多数商业 GIS 软件包都具有将一个多边形数据集拆分为另一个数据集的工具。搜索做交叉点的工具。

如果您使用的是 OpenSource,请查看 QGIS 和 http://www.ftools.ca,它们“执行地理处理操作,包括交叉点、差分、联合、融合和裁剪。”我自己没有使用过后者。

看看:http://postgis.refractions.net/docs/ch04.html#id2790790 了解为什么大型功能不好。

过滤和相交

这里有更多关于 Filter 子句的内容 - http://blogs.msdn.com/b/isaac/archive/2010/03/04/filter-one-odd-duck.aspx

空间索引

要检查的其他一点是空间索引实际上正在查询计划中使用。您可能必须强制查询使用带有 WITH 子句的索引:

http://blogs.msdn.com/b/isaac/archive/2008/08/29/is-my-spatial-index-being-used.aspx

以下索引的更多详细信息:

http://blogs.msdn.com/b/isaac/archive/2009/05/28/sql-server-spatial-indexing.aspx

还可以尝试为您的数据运行 sp_help_spatial_geometry_index,以查看用于空间索引的设置

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

使用一些测试几何运行此 SP 会产生各种统计信息,以尝试根据数据调整索引。完整的属性列表位于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 可能值得重新创建表/数据库并从头开始尝试索引。

Total_Number_Of_ObjectCells_In_Level0_In_Index 60956 是许多要在级别 0 返回的要素。它们很可能在空间索引范围之外或为空。然后它在所有这些特征上运行 Intersect (Number_Of_Times_Secondary_Filter_Is_Called 60956),这可以解释为什么它很慢。即使文档声称空特征没有性能影响 - 我相信它仍然需要查找记录,即使没有执行相交。

NULL 和空实例被计算在内 处于 0 级,但不会影响 表现。 0级将有尽可能多的 单元格为 NULL 和空实例 基表。

我相信 0.003281055 的 Primary_Filter_Efficiency 表示 0.03% 的效率!

有几件事可以尝试:

  1. SELECT * FROM sys.spatial_indexes 有什么奇怪的地方吗?
  2. MakeValid 语句:

    UPDATE MyTable SET GeomFieldName = GeomFieldName.MakeValid()

  3. 重置/仔细检查 SRID:

    更新 MyTable SET GeomFieldName.STSrid = 4326

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

    ALTER TABLE MyTable 添加 MinX AS (CONVERT(int,GeomFieldName.STenvelope().STPointN((1)).STX,0)) 持久化 更改表 MyTable 添加 MinY AS (CONVERT(int,GeomFieldName.STenvelope().STPointN((1)).STY,0)) 持久化 更改表 MyTable 添加 MaxX AS (CONVERT(int,GeomFieldName.STenvelope().STPointN((3)).STX,0)) 持久化 更改表 MyTable 添加 MaxY AS (CONVERT(int,GeomFieldName.STenvelope().STPointN((3)).STY,0)) 持久化

【讨论】:

  • 查询是由 GeoServer 生成的,我使用它来生成使用 Bing 地图呈现的图块。分割多边形是我已经考虑过的一件事,但据我所知,我已经推迟了一两天来完善查询/构建一个工具来完成它。 Filter 的查询时间比 STIntersects 的查询时间短 50%。
  • 我仍然对为什么过滤器返回这么多记录感到困惑。数据集中有很多大多边形,但是 SQL 索引如何工作的描述让我预计过滤器会返回几千个,而不是 60,000+(整个数据集的 60%)。
  • 我刚刚做了一个快速查询,看看我有多少真正巨大的多边形。从每个多边形的包络面积来看,106,000 个多边形中只有 3 个占据了地球 50% 以上的面积,其中只有 23 个大于地球的八分之一。
  • 我意识到我误解了你关于查询时间的问题。将过滤器与 STIntersects 组合使用与 STIntersects 本身相同的时间,无论是将它们与子查询组合还是将两者都添加到 WHERE 子句中。这并不奇怪,因为我读到的内容是,当有可用的空间索引时,STIntersects 无论如何都会在内部执行过滤器。仅做一个过滤器需要 6 秒,任何涉及添加 STIntersects 的操作都需要 12 秒。
  • 我已经添加了过滤器如何工作的链接,以及分割多边形 - 应该不需要开发自己的工具来执行此操作。
【解决方案2】:

在您使用的索引查询中:

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

BOUNDING_BOX 因此映射到:

xmin = -90
ymin = -180
xmax = 90
ymax = 180
  • 经度(-180 至 180 - 指定东/西 Meridian) 应该映射到 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),
...

这应该会创建一个适合您的数据的索引,并且意味着您的所有功能都包含在索引中。

【讨论】:

  • 谢谢,谢谢,谢谢。看来我用来导入形状文件的工具最初使用错误的边界框创建了索引,从那以后我一直在盲目地复制同样的错误。
【解决方案3】:

我也发现很难“猜测”特定几何表的适当空间索引是什么。我尝试使用 sp_help_spatial_geometry_index 存储过程进行更有根据的猜测。所有这一切都告诉我在每次“GUESS”之后我的空间索引的表现有多差。即使我通过仅考虑 2-8 个 CELLS_PER_OBJECT 来限制我的选项,单独会给出 567 个排列(选择 3 种类型 4 次 = 81。然后乘以 7 个 CELLS_PER_OBJECT 选项)。我决定让 SQL Server 为我做实验并给我一些经验证据。我创建了一个存储过程,它将遍历排列并为每个排列重建空间表上的空间索引。然后它将使用两个提供的几何实例测试空间索引的每个排列的查询性能。我选择了一个包含整个数据集的几何实例,然后选择了另一个包含一小部分数据集的实例。 proc 在每个实例上使用 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 次将需要很长时间。将它从命令行启动,或者让它在你做其他事情时运行。如果它是您将经常使用的数据集并且几何形状保持相似,那么运行 proc 所花费的时间将是值得的。结果表以毫秒为单位显示性能。

【讨论】:

  • 感谢您的回答!与其使用 Dropbox(被许多雇主阻止),是否可以在这里提供存储过程的要点?或者,您可以考虑使用 Github Gist (gist.github.com) 作为替代托管位置。
  • 感谢您的建议。刚刚更新了指向该网站的超链接。
  • @GreenGeo 太棒了!我破解了您的存储过程以包含更多输出:geonet.esri.com/blogs/HackingArcSDE/2015/07/07/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 1970-01-01
  • 2013-07-10
相关资源
最近更新 更多