Выбор хорошего пространственного индекса SQL Server 2008 с большими многоугольниками

Я получаю удовольствие, пытаясь выбрать подходящую настройку пространственного индекса SQL Server 2008 для набора данных, с которым я имею дело.

Набор данных - это полигоны, представляющие контуры по всему земному шару. В таблице 106 000 строк, многоangularьники хранятся в геометрическом поле.

У меня проблема в том, что многие полигоны покрывают большую часть земного шара. По-видимому, это очень затрудняет получение пространственного индекса, который устранит множество строк в основном фильтре. например, посмотрите на следующий запрос:

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 строк.

Замена Filter() на STIntersects(), конечно, возвращает только те два полигона, которые мне нужны, но, конечно, это занимает гораздо больше времени (Filter() - 6 секунд, STIntersects() - 12 секунд).

Может кто-нибудь подсказать, есть ли настройка пространственного индекса, которая может улучшиться на 60000 строк, или мой набор данных просто не подходит для пространственной индексации 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

Я, очевидно, сделал это для всех 16 ограничительных рамок в сетке 4х4 Конечным результатом является то, что у меня есть новая таблица с ~ 107 000 строк (что подтверждает, что у меня на самом деле не было много огромных полигонов).

Я добавил пространственный индекс с 1024 ячейками на объект и низкими, низкими, низкими, низкими для ячеек на уровень.

Однако, как ни странно, эта новая таблица с разделенными полигонами по-прежнему работает так же, как и старая. Выполнение указанного выше фильтра. По-прежнему возвращает ~ 60000 строк. Я действительно не понимаю этого вообще, ясно, что я не понимаю, как на самом деле работает пространственный индекс.

Как это ни парадоксально, хотя .Filter() по-прежнему возвращает ~ 60000 строк, но производительность повысилась..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, это в моем наборе разделенных данных, где ни один многоangularьник не занимает более 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 Пересекающиеся_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

При рендеринге набор данных выглядит так: 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 объекта, которые пересекаются с более чем 1 ячейкой в сетке 4x4. Так как же, буквально, может первичный фильтр вернуть 60 000 объектов для запроса, рассматривающего небольшой прямоangularьник шириной 200 миль?

На данный момент похоже, что я мог бы написать свою собственную схему индексации, которая работала бы лучше, чем SQL Server для этих функций.

Ответы

Ответ 1

В запросе вашего индекса вы используете:

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 - обозначение Восток/Запад Меридиан) должен отображаться на 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),
...

Это должно создать индекс, который соответствует вашим данным, и означает, что все ваши функции покрываются индексом.

Ответ 2

Разделение данных

Если запрос предназначен для отображения данных, вы можете разделить свои большие полигоны с помощью сетки. Это было бы очень быстро получить с индексом. Вы можете удалить контуры, чтобы функции все равно выглядели смежными.

Большинство коммерческих пакетов ГИС будут иметь инструменты для разделения одного набора данных полигона на другой. Найдите инструменты, которые выполняют пересечения.

Если вы используете OpenSource, посмотрите на QGIS и http://www.ftools.ca, которые "выполняют операции геообработки, включая пересечениясильные > , различия, объединения, растворения и отсечения". Я сам не использовал последний.

Посмотрите на: http://postgis.refractions.net/docs/ch04.html#id2790790 за то, почему большие функции плохие.

Фильтр и пересечения

Здесь в разделе "Фильтр" есть больше: 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) во всех этих функциях, что объясняет, почему оно медленное. Несмотря на то, что docs заявляют, что нет производительности для нулевых функций, я считаю, что он все равно должен искать записи, даже если пересечение не выполняется.

подсчитываются NULL и пустые экземпляры на уровне 0, но не повлияет представление. Уровень 0 будет иметь столько же ячейки как NULL и пустые экземпляры в базовая таблица.

Primary_Filter_Efficiency 0.003281055 Я считаю, что эффективность равна 0,03%!

Несколько вещей, чтобы попробовать:

  • Что-нибудь странное из SELECT * FROM sys.spatial_indexes?
  • Оператор MakeValid:

    UPDATE MyTable SET GeomFieldName = GeomFieldName.MakeValid()

  • Reset/двойная проверка SRID:

    UPDATE MyTable SET GeomFieldName.STSrid = 4326

  • Добавьте несколько полей, чтобы показать экстенты ваших функций. Это может указывать на проблемы/геометрию NULL.

    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)) ПЕРСИСТЕМА ALTER TABLE MyTable ADD MaxX AS (CONVERT (int, GeomFieldName.STEnvelope(). STPointN ((3)). STX, 0)) ПЕРСИСТЕМА ALTER TABLE MyTable ADD MaxY AS (CONVERT (int, GeomFieldName.STEnvelope(). STPointN ((3)). STY, 0)) PERSISTED

Ответ 3

Мне тоже было очень сложно "УГАДАТЬ", какой подходящий пространственный индекс будет для конкретной таблицы геометрий. Я попытался сделать более обоснованные предположения, используя хранимую процедуру sp_help_spatial_geometry_index. Все это сказало мне, насколько плохо мой пространственный индекс выполнял после каждого "УГАДАЙ". Даже если бы я ограничил свои параметры только рассмотрением 2-8 CELLS_PER_OBJECT, это само по себе дает 567 перестановок (3 типа выбраны 4 раза = 81. Затем умножьте на 7 опций CELLS_PER_OBJECT). Я решил, что я позволю SQL-серверу экспериментировать и дать мне некоторые эмпирические данные. Я создал хранимую процедуру, которая будет вращаться через перестановки и перестроить пространственный индекс в пространственной таблице для каждого из них. Затем он будет проверять выполнение запросов каждой перестановки пространственного индекса, используя два поставленных экземпляра геометрии. Я выбрал один экземпляр геометрии, который включал весь набор данных, а затем еще один экземпляр, который включал меньшую часть набора данных. Proc использует STIntersect() 4 раза для каждого экземпляра, а затем записывает результаты в таблицу. Затем вы можете запросить таблицу результатов, чтобы узнать, какой пространственный индекс лучше всего подходит для вашего конкретного набора данных. Попробуйте и дайте мне знать, есть ли у вас какие-либо предлагаемые улучшения или наблюдения.

Создайте proc, используя этот 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. Таблица результатов показывает производительность в миллисекундах.