Ogranicz rzędy poprzez funkcję przestrzenną


9

Próbuję poprawić wydajność poniższego zapytania. Bez względu na to, jak piszę zapytanie (podzapytanie w klauzuli FROM, podzapytanie w klauzuli WHERE) postgres nalega na uruchomienie wszystkich ~ 570K wierszy przez kosztowną funkcję ST_DWITHIN, nawet jeśli jest tylko 60 wierszy, gdzie county = 24. Jak mogę filtrować postgres według hrabstwa = 24 PRZED uruchomieniem funcji postgis, która wydaje mi się znacznie szybsza i znacznie wydajniejsza? 700ms nie jest powodem do zbytniego niepokoju, ale kiedy ta tabela rośnie do 10M +, martwię się o wydajność.

Należy również zauważyć, p.id jest kluczem podstawowym, p.zipcode jest indeksem fk, z.county jest indeksem fk, a p.geom ma indeks GiST.

Pytanie:

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
    )

OBJAŚNIJ ANALIZĘ:

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

Może spróbuj zmienić wiersz „wskaż jako p lewy dołącz kod pocztowy jako z” na coś w stylu „wskaż jako p lewy dołącz (WYBIERZ * z kodu pocztowego GDZIE zipcode.county = 24) jako z”?
weiji14

Po prostu spróbowałem, te same wyniki. Kiedy pointkopiuję same ~ 60 wierszy, w których county = 24, do nowej tabeli same, zapytanie zajmuje tylko 0,453 ms w porównaniu do 724, więc jest zdecydowanie duża różnica.
Josh

1
Powinieneś używać count(*)ze względu na styl. Jeśli idto pkid, jak mówisz, NOT NULLoznacza to, że są takie same. Poza tym count(id)ma tę wadę, że musisz zadać to pytanie, jeśli idjest zerowe.
Evan Carroll,

1
Czy mogę zapytać, dlaczego używasz lewego sprzężenia zewnętrznego? Spróbuj zmienić go na łączenie wewnętrzne ... Wyniki powinny być identyczne
MickyT

Jeśli z.country jest czynnikiem ograniczającym, sugeruję, abyś umieścił to na pierwszym miejscu w zapytaniu CTE, a następnie po prostu sprawdź te wyniki pod kątem przecięcia z punktem zainteresowania. Ponieważ wskaźnik przestrzenny jest prawdopodobnie mniej selektywny niż powiat = 24, w tym przypadku przeszkadza.
John Powell,

Odpowiedzi:


3

Możesz zobaczyć problem z oczekiwaną a rzeczywistą liczbą wierszy. Planista uważa, że ​​istnieje 7437 wierszy, jednak jest ich tylko 63. Statystyki są wyłączone. Co ciekawe, nie korzysta z wyszukiwania indeksu (indeksu) obwiedni, z którym DWithinmożna wkleić wynik \d point. Jaka wersja PostGIS i PostgreSQL?

Spróbuj uruchomić ANALYZE point. Czy otrzymujesz ten sam plan, gdy podnosisz stan w górę?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24

Przebiegłem analizę, a także wypróbowałem nowy warunek ORAZ w trybie WŁ., Ale nadal otrzymywałem 700 ms. To jest PGSQL 9.4 i PostGIS 2.2.
Josh

2

Na marginesie, istnieje uzasadniona szansa, że ​​to zachowanie zostanie zmodyfikowane w PostGIS 2.3.0, jeśli chcesz nazwać to błędem.

Z dokumentacji na PostgreSQL

Liczba dodatnia podająca szacowany koszt wykonania funkcji, w jednostkach cpu_operator_cost. Jeśli funkcja zwraca zestaw, jest to koszt za zwrócony wiersz. Jeśli koszt nie zostanie określony, zakłada się 1 jednostkę dla języka C i funkcji wewnętrznych oraz 100 jednostek dla funkcji we wszystkich innych językach. Większe wartości powodują, że planista stara się unikać oceny funkcji częściej niż to konieczne.

Tak więc domyślny koszt wynosił 1 (bardzo tanio). D_Withinkorzystanie z indeksu GIST jest bardzo tanie. Ale to zostało zwiększone do 100 (przez proxy wewnętrzne _ST_DWithin).

Sam nie jestem wielkim fanem metody CTE. CTE to płot optymalizacyjny. Tak więc robienie tego w taki sposób eliminuje potencjalną przestrzeń do przyszłej optymalizacji. Jeśli saner defaults to naprawi, wolę uaktualnić. Pod koniec dnia musimy wykonać zadanie i ta metoda najwyraźniej działa dla Ciebie.


1

Dzięki podpowiedzi Johna Powella poprawiłem zapytanie, aby umieścić warunek ograniczenia hrabstwa w zapytaniu z / CTE, a to poprawiło wydajność nieco do 222 ms w porównaniu z 700. Nadal daleko mi do 0,74 ms, kiedy otrzymuję dane własny stół. Nadal nie jestem pewien, dlaczego planista nie ogranicza zbioru danych przed uruchomieniem drogiej funkcji Postgis, i będę musiał spróbować z większymi zestawami danych, gdy je mam, ale wydaje się, że na razie jest to rozwiązanie tej wyjątkowej sytuacji.

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
Musielibyśmy zobaczyć wszystkie trzy plany zapytań i schemat tabeli (wymagany w mojej odpowiedzi \ punkt).
Evan Carroll,

0

Powinieneś utworzyć indeks na zipcode(county, zipcode), który powinien dać indeks tylko skanować na z.

Można też poeksperymentować z btree_gistrozszerzeniem tworząc zarówno point(zipcode, geom)indeks lub point(geom, zipcode)i zipcode(zipcode, county)indeks.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.