Выбор хорошего пространственного индекса SQL Server 2008 с большими полигонами - PullRequest
12 голосов
/ 27 мая 2010

Я получаю удовольствие, пытаясь выбрать подходящую настройку пространственного индекса 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 строк.

Замена 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 ограничивающих рамок в сетке 4x4. Конечным результатом является то, что у меня есть новая таблица с ~ 107 000 строк (что подтверждает, что у меня не было много огромных полигонов).

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

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

Как это ни парадоксально, хотя .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, это в моем наборе разделенных данных, где ни один полигон не занимает более 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 1Total_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

При визуализации набор данных выглядит следующим образом: альтернативный текст http://norman.cx/photos/links/wms.png

Дальнейшие исследования:

Я по-прежнему озадачен низкой производительностью первичного фильтра с этими данными. Итак, я сделал тест, чтобы увидеть, как именно мои данные распадаются. С моими оригинальными нерасщепленными функциями я добавил в таблицу столбец «ячейки». Затем я выполнил 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 объектов для запроса, смотрящего на маленький прямоугольник шириной 200 миль?

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

Ответы [ 3 ]

12 голосов
/ 07 июня 2010

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

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),
...

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

5 голосов
/ 30 мая 2010

Данные расщепления

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

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

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

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

Primary_Filter_Efficiency, равный 0,003281055, я считаю, указывает на 0,03% эффективности!

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

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

    ОБНОВЛЕНИЕ 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)) PERSISTED ALTER TABLE MyTable ДОБАВЬТЕ MinY AS (CONVERT (int, GeomFieldName.STEnvelope (). STPointN ((1)). STY, 0)) PERSISTED ALTER TABLE MyTable ДОБАВИТЬ MaxX AS (CONVERT (int, GeomFieldName.STEnvelope (). STPointN ((3)). STX, 0)) PERSISTED ALTER TABLE MyTable ДОБАВИТЬ MaxY AS (CONVERT (int, GeomFieldName.STEnvelope (). STPointN ((3)). STY, 0)) PERSISTED

0 голосов
/ 06 апреля 2013

Мне тоже было очень трудно "УГнать", каким будет соответствующий пространственный индекс для конкретной таблицы геометрий. Я попытался сделать более образованные предположения, используя хранимую процедуру 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 раз займет много времени. Отключите его от командной строки или просто запустите, пока вы занимаетесь другими делами. Если это набор данных, который вы собираетесь использовать часто, а геометрия остается схожей, это будет стоить времени, необходимого для запуска процедуры. Таблица результатов показывает производительность в миллисекундах.

...