Улучшение производительности STIntersects

11

Таблица T_PINимеет 300 000 пинов и T_POLYGONимеет 36 000 полигонов. T_PINимеет этот индекс:

CREATE SPATIAL INDEX [T_PIN_COORD] ON [dbo].[T_PIN]
(
[Coord]
)USING  GEOGRAPHY_GRID 
WITH (GRIDS =(LEVEL_1 = HIGH,LEVEL_2 = HIGH,LEVEL_3 = HIGH,LEVEL_4 = HIGH), 
CELLS_PER_OBJECT = 128, PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY];

T_POLYGON имеет:

CREATE SPATIAL INDEX [T_POLYGON_COORD] ON [dbo].[T_POLYGON]
(
[COORD]
)USING  GEOGRAPHY_GRID 
WITH (GRIDS =(LEVEL_1 = HIGH,LEVEL_2 = HIGH,LEVEL_3 = HIGH,LEVEL_4 = HIGH), 
CELLS_PER_OBJECT = 128, PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY];

Запрос для поиска пересечения T_PINи выполнения T_POLYGONзанимает более 45 минут:

SELECT COUNT(*)
FROM T_PIN 
INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.COORD) = 1;

Результат - 4 438 318 строк.

Как я могу ускорить этот запрос?

seb49
источник
Вы пытались использовать `T_POLYGON.Coord.STIntersects (T_PIN.COORD) = 1 '?
Трэвис
Мне было бы интересно увидеть ваш план запроса. Я работаю с Postgres, но должен выполнять похожие запросы, но со значительно большими наборами данных. Я придумала технику, которая сводит мои худшие из них к примерно 2 дням (что, к сожалению, включает в себя сценарии), но мне было бы интересно сначала увидеть ваш план запросов.
Джон Пауэлл
Умножая многоугольники в моих двух таблицах вместе, я получаю число, которое в 7000 раз больше, с точки зрения количества потенциальных пересечений, чем у вас в вашей комбинации, поэтому я думаю, что в этом свете мои два дня выглядят довольно хорошо. Однако, не видя план запроса и не зная среднего числа точек на многоугольник, будет сложно найти конкретные решения.
Джон Пауэлл

Ответы:

7

Во-первых, проверьте, используется ли пространственный индекс, посмотрев план выполнения запроса, и посмотрите, есть ли элемент поиска кластерного индекса (пространственный).

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

1) Добавьте новый столбец географии и геометрии в таблицу [dbo]. [T_POLYGON]:

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeom geometry;
ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog geography;

2) Создайте ограничивающие полигоны (это включает в себя первоначальное преобразование в геометрию, чтобы воспользоваться STEnvelope ()):

UPDATE [dbo].[T_POLYGON] SET SimplePolysGeom = geometry::STGeomFromWKB(
    COORD.STAsBinary(), COORD.STSrid).STEnvelope();

UPDATE [dbo].[T_POLYGON] SET SimplePolysGeog = geography::STGeomFromWKB(
    SimplePolysGeom.STAsBinary(), SimplePolysGeom.STSrid);

3) Создайте пространственный индекс в столбце упрощенной географии

4) Получите пересечения с этим упрощенным столбцом географии, затем снова отфильтруйте по соответствующим типам данных географии. Примерно как то так:

;WITH cte AS
(
   SELECT pinID, polygonID FROM T_PIN INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.SimplePolysGeog ) = 1
)
SELECT COUNT(*)
FROM T_PIN 
INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.COORD) = 1
    AND T_PIN.pinID IN (SELECT pinID FROM cte)
    AND T_POLYGON.polygonID IN (SELECT polygonID FROM cte)

РЕДАКТИРОВАТЬ : вы можете заменить (1) и (2) с этим вычисленным, постоянным столбцом. Благодарю Пола Уайта за предложение.

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog AS  ([geography]::STGeomFromWKB([geometry]::STGeomFromWKB([COORD].[STAsBinary](),[COORD].[STSrid]).STEnvelope().STAsBinary(),(4326))) PERSISTED
g2server
источник
Да, это более или менее то, что я получал. Проблема, которую я обнаружил при пространственном «объединении» двух наборов таблиц с широкой зоной покрытия, заключается в том, что оптимизатор часто выполняет два полных сканирования таблицы и загрузки точек в тестах полигонов.
Джон Пауэлл
2

Подобные запросы часто занимают много времени из-за сложности полигонов. Я видел, как сложные береговые линии (например) требовали возрастов, чтобы проверить точки, которые находятся вблизи их границ, и им приходилось масштабировать многие уровни, чтобы определить, находится ли точка внутри или снаружи.

... чтобы вы могли попробовать .Reduce()полигоны, чтобы посмотреть, поможет ли это.

Подробнее об этой функции можно узнать на сайте http://msdn.microsoft.com/en-us/library/cc627410.aspx.

Роб Фарли
источник
1

Согласно документам Microsoft, пространственные индексы будут использоваться с типами географии в следующих методах, когда они появляются в начале предиката сравнения с WHEREпредложением:

  • STIntersects
  • STDistance
  • STEquals

Только методы с геометрическими типами (ограниченный список) будут инициировать использование пространственного индекса JOIN ... ON, поэтому измените код, который будет использоваться, WHERE geog1.STIntersects(geog2) = 1и это должно повысить скорость.

Я также рекомендую принять совет в ответе g2server и добавить следующее для фильтрации и добавить пространственный индекс на него

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog AS
     ([geography]::STGeomFromWKB([geometry]::STGeomFromWKB([COORD].[STAsBinary](),
                                                           [COORD].[STSrid])
                 .STEnvelope().STAsBinary(),(4326))) PERSISTED

тогда вы могли бы получить запрос, подобный следующему (я написал это сообщение быстро и еще не проверял, это просто что-то попробовать, потому что я видел, что ваш запрос и ответы с наибольшим количеством ответов используют JOIN ON пространственный op = 1, который не будет использовать пространственный индекс):

SELECT   
     (SELECT p2.polygon_id
      FROM   T_Polygon p2
      WHERE  p2.coords.STIntersects(t.coords) = 1),
     t.pin_id
FROM     T_PIN t
WHERE    
     (SELECT t.coords.STIntersects(p.coords)
      FROM   T_POLYGON p
      WHERE  t.coords.STIntersects(p.SimplePolysGeog) = 1) = 1

К вашему сведению: вышеупомянутое не работает, если в SimplePolysGeogконечном итоге перекрывается (как в выводе может быть в двух упрощенных геог, просто запустил это на людях на участках в состоянии и так как обычные границы полиса, ограничивающие прямоугольники перекрываются), поэтому в большинстве случаев В некоторых случаях будет выдано сообщение о том, что подзапрос вернул более одного результата.

Из обзора пространственных индексов MS Docs :

Географические методы, поддерживаемые пространственными индексами

При определенных условиях пространственные индексы поддерживают следующие ориентированные на множество географические методы: STIntersects (), STEquals () и STDistance (). Для поддержки пространственного индекса эти методы должны использоваться в предложении WHERE запроса, и они должны встречаться в предикате следующей общей формы:

geography1.method_name (geography2) comparison_operatorvalid_number

Чтобы вернуть ненулевой результат, geography1 и geography2 должны иметь одинаковый идентификатор пространственной привязки (SRID) . В противном случае метод возвращает NULL.

Пространственные индексы поддерживают следующие формы предикатов:


Запросы, которые используют пространственные индексы

Пространственные индексы поддерживаются только в запросах, которые включают индексированный пространственный оператор в предложении WHERE. Например, такой синтаксис, как:

[spatial object].SpatialMethod([reference spatial object]) [ = | < ] [const literal or variable]

Оптимизатор запросов понимает коммутативность пространственных операций (то есть @a.STIntersects(@b) = @b.STInterestcs(@a)). Однако пространственный индекс не будет использоваться, если начало сравнения не содержит пространственный оператор (например WHERE 1 = spatial op, не будет использовать пространственный индекс). Чтобы использовать пространственный индекс, перепишите сравнение (например WHERE spatial op = 1).

...

Следующий запрос будет работать в случае SimplePolysGeogsналожения:

;WITH cte AS
(
   SELECT T_PIN.PIN_ID, 
          T_POLYGON.POLYGON_ID, 
          T_POLYGON.COORD 
   FROM T_PIN 
   INNER JOIN T_POLYGON
   ON T_PIN.COORD.STIntersects(T_POLYGON.SimplePolysGeog) = 1
)

SELECT COUNT(*)
FROM T_PIN 
INNER JOIN cte
ON T_PIN_PIN_ID = cte.PIN_ID
where cte.[COORD].STIntersects(T_PIN.COORD) = 1
pbordeaux
источник