Предельные ряды через пространственную функцию

9

Я пытаюсь улучшить производительность для запроса ниже. Независимо от того, как я пишу запрос (подзапрос в предложении FROM, подзапрос в предложении WHERE) postgres настаивает на запуске всех ~ 570K строк через дорогую функцию ST_DWITHIN, даже если есть только 60 строк, в которых county = 24. Как я могу заставить postgres фильтровать на county = 24 ПЕРЕД выполнением функции postgis, которая, как мне кажется, будет намного быстрее и намного эффективнее? 700 мс не является причиной для слишком большого беспокойства, но поскольку эта таблица увеличивается до 10 М +, я беспокоюсь о производительности.

Также следует отметить, что p.id - это первичный ключ, p.zipcode - это индекс fk, z.county - это индекс fk, а p.geom имеет индекс GiST.

Запрос:

EXPLAIN ANALYZE
  SELECT count(p.id)
  FROM point AS p
  LEFT JOIN zipcode AS z
    ON p.zipcode = z.zipcode
  WHERE z.county = 24
    AND ST_DWithin(
      p.geom, 
      ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269), 
      16090.0,
      false
    )

ОБЪЯСНИТЬ АНАЛИЗ:

Aggregate  (cost=250851.91..250851.92 rows=1 width=4) (actual time=724.007..724.007 rows=1 loops=1)
  ->  Hash Join  (cost=152.05..250851.34 rows=228 width=4) (actual time=0.359..723.996 rows=51 loops=1)
        Hash Cond: ((p.zipcode)::text = (z.zipcode)::text)
        ->  Seq Scan on point p  (cost=0.00..250669.12 rows=7437 width=10) (actual time=0.258..723.867 rows=63 loops=1)
              Filter: (((geom)::geography && '0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography) AND ('0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography && _st_expand((geom)::geography, 16090::double precision)) AND _st_dwithin((g (...)
              Rows Removed by Filter: 557731
        ->  Hash  (cost=151.38..151.38 rows=54 width=6) (actual time=0.095..0.095 rows=54 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 3kB
              ->  Bitmap Heap Scan on zipcode z  (cost=4.70..151.38 rows=54 width=6) (actual time=0.023..0.079 rows=54 loops=1)
                    Recheck Cond: (county = 24)
                    Heap Blocks: exact=39
                    ->  Bitmap Index Scan on fki_zipcode_county_foreign_key  (cost=0.00..4.68 rows=54 width=0) (actual time=0.016..0.016 rows=54 loops=1)
                          Index Cond: (county = 24)
Planning time: 0.504 ms
Execution time: 724.064 ms
мистифицировать
источник
Может быть, попытаться изменить строку «точка как p в левом соединении zipcode как z» на что-то вроде «точка как p в левом соединении (SELECT * FROM zipcode WHERE zipcode.county = 24) как z»?
weiji14
Только что попробовал, те же результаты. Когда я сам pointкопирую ~ 60 строк, в которых county = 24, в новую таблицу, запрос занимает всего 0,403 мс по сравнению с 724, поэтому, безусловно, есть большая разница.
Джош
1
Вы должны использовать count(*)как вопрос стиля. Если idэто pkid, как вы говорите, это NOT NULLозначает, что они одинаковы. За исключением count(id)того недостатка, что вы должны задать этот вопрос, если idобнуляется.
Эван Кэрролл
1
Могу я спросить, почему вы используете левое внешнее соединение? Попробуйте изменить его на внутреннее соединение ... Результаты должны быть одинаковыми
MickyT
Если ограничивающим фактором является z.country, я бы посоветовал вам сначала указать это в запросе CTE, а затем просто проверить эти результаты на предмет пересечения с вашей точкой интереса. Поскольку пространственный индекс, вероятно, менее избирателен, чем округ = 24 в этом случае, он только мешает.
Джон Пауэлл

Ответы:

3

Вы можете увидеть проблему с ожидаемым и фактическим количеством строк. Планировщик считает, что есть 7 437 строк, но только 63. Статистика отключена. Интересно также, что он не использует поиск по индексу (индексу) с помощью ограничивающего прямоугольника, с помощью которого DWithinможно вставить результат \d point. Какая версия PostGIS и PostgreSQL?

Попробуйте запустить ANALYZE point. Вы получаете тот же план, когда вы перемещаете условие вверх?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24
Эван Кэрролл
источник
Я запустил анализ, а также попробовал новое условие И во включенном состоянии, но время выполнения все равно составляло 700 мс. Это PGSQL 9.4 и PostGIS 2.2.
Джош
2

Как примечание стороны, есть разумный шанс , что такое поведение изменяется в PostGIS 2.3.0 , если вы хотите назвать это ошибкой.

Из документации по PostgreSQL

Положительное число, представляющее оценочную стоимость выполнения функции в единицах cpu_operator_cost. Если функция возвращает набор, это стоимость за возвращенную строку. Если стоимость не указана, предполагается 1 единица для языка C и внутренних функций и 100 единиц для функций на всех других языках. Большие значения заставляют планировщика избегать оценки функции чаще, чем это необходимо.

Таким образом, стоимость по умолчанию была 1 (очень дешево). D_WithinИспользование индекса GIST очень дешево. Но это было увеличено до 100 (по доверенности внутреннего _ST_DWithin).

Я сам не большой поклонник метода CTE. CTE - это забор оптимизации. Выполнение этого таким образом устраняет потенциальную возможность для дальнейшей оптимизации. Если Saner по умолчанию исправить это, я бы предпочел обновить. В конце концов, мы должны выполнить работу, и этот метод явно работает для вас.

Эван Кэрролл
источник
1

Благодаря подсказке Джона Пауэлла я пересмотрел запрос, чтобы поместить условие ограничения округа в запрос с / CTE, и это значительно улучшило производительность до 222 мс против 700. До далека от 0,74 мс я получаю, когда данные находятся в его собственный стол. Я до сих пор не уверен, почему планировщик не ограничивает набор данных перед запуском дорогой функции postgis, и мне придется попробовать с большими наборами данных, когда они у меня есть, но сейчас это решение этой уникальной ситуации.

with points as (
   select p.id, p.geom from point p inner join zipcode z
   on p.zipcode = z.zipcode
   where county = 24
   ) 


SELECT count(points.id)
FROM points
WHERE ST_DWITHIN(points.geom, (ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269)), 16090.0, false)
мистифицировать
источник
1
Нам нужно увидеть все три плана запросов и схему таблицы (запрошено в моем ответе \ d).
Эван Кэрролл
0

Вы должны создать индекс для zipcode(county, zipcode), который должен дать вам индекс только для сканирования по z.

Вы также можете поэкспериментировать с btree_gistрасширением создающего либо point(zipcode, geom)индекс или point(geom, zipcode)и zipcode(zipcode, county)индекс.

Якуб Кания
источник