Zeilen durch räumliche Funktion begrenzen

9

Ich versuche, die Leistung für die folgende Abfrage zu verbessern. Unabhängig davon, wie ich die Abfrage schreibe (Unterabfrage in der FROM-Klausel, Unterabfrage in der WHERE-Klausel), besteht postgres darauf, alle ~ 570K-Zeilen durch die teure ST_DWITHIN-Funktion auszuführen, obwohl es nur 60 Zeilen gibt, in denen county = 24 ist. Wie kann ich Postgres dazu bringen, nach County = 24 zu filtern, bevor ich die Postgis-Funktion durchlaufe, die meiner Meinung nach viel schneller und weitaus effizienter ist? 700 ms sind kein Grund zur Sorge, aber da diese Tabelle auf 10 Millionen + anwächst, mache ich mir Sorgen um die Leistung.

Zu beachten ist auch, dass p.id ein Primärschlüssel ist, p.zipcode ein fk-Index ist, z.county ein fk-Index ist und p.geom einen GiST-Index hat.

Abfrage:

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
    )

ANALYSE ERKLÄREN:

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
Josh
quelle
Versuchen Sie vielleicht, die Zeile "Punkt als p links Join Postleitzahl als z" in etwas wie "Punkt als p links Join (SELECT * FROM Postleitzahl WHERE Postleitzahl.county = 24) als z" zu ändern?
Weiji14
Ich habe es gerade versucht, die gleichen Ergebnisse. Wenn ich die ~ 60 pointZeilen, in denen Grafschaft = 24 ist, alleine in eine neue Tabelle kopiere, dauert die Abfrage nur 0,453 ms im Vergleich zu 724, sodass es definitiv einen großen Unterschied gibt.
Josh
1
Sie sollten count(*)als eine Frage des Stils verwenden. Wenn ides sich um ein pkid handelt, wie Sie sagen, NOT NULLbedeutet dies, dass sie gleich sind. Außer count(id)hat den Nachteil, dass Sie diese Frage stellen müssen, wenn idnullbar ist.
Evan Carroll
1
Kann ich fragen, warum Sie eine linke äußere Verknüpfung verwenden? Versuchen Sie es in einen inneren Join zu ändern ... Die Ergebnisse sollten identisch sein
MickyT
Wenn z.country der begrenzende Faktor ist, würde ich vorschlagen, dass Sie dies zuerst in eine CTE-Abfrage einfügen und dann einfach diese Ergebnisse auf einen Schnittpunkt mit Ihrem Sonderziel überprüfen. Da der räumliche Index in diesem Fall wahrscheinlich weniger selektiv ist als Grafschaft = 24, stört er nur.
John Powell

Antworten:

3

Sie können das Problem mit den erwarteten und tatsächlichen Zeilenzahlen sehen. Der Planer glaubt, dass es 7.437 Zeilen gibt, aber nur 63. Die Statistiken sind aus. Interessanterweise wird auch keine Bounding-Box-Indexsuche (Indexsuche) verwendet, mit der DWithinSie das Ergebnis einfügen können \d point. Welche Version von PostGIS und PostgreSQL?

Versuche zu rennen ANALYZE point. Erhalten Sie den gleichen Plan, wenn Sie die Bedingung nach oben verschieben?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24
Evan Carroll
quelle
Ich habe Analyse ausgeführt und auch die neue UND-Bedingung in EIN ausprobiert, bekam aber immer noch 700 ms Laufzeit. Dies ist PGSQL 9.4 und PostGIS 2.2.
Josh
2

Als Randnotiz besteht eine vernünftige Wahrscheinlichkeit, dass dieses Verhalten in PostGIS 2.3.0 geändert wird, wenn Sie es als Fehler bezeichnen möchten.

Aus den Dokumenten zu PostgreSQL

Eine positive Zahl, die die geschätzten Ausführungskosten für die Funktion in Einheiten von cpu_operator_cost angibt. Wenn die Funktion einen Satz zurückgibt, sind dies die Kosten pro zurückgegebener Zeile. Wenn die Kosten nicht angegeben sind, wird 1 Einheit für C-Sprache und interne Funktionen und 100 Einheiten für Funktionen in allen anderen Sprachen angenommen. Bei größeren Werten versucht der Planer zu vermeiden, die Funktion häufiger als nötig zu bewerten.

Die Standardkosten waren also 1 (sehr billig). D_WithinDie Verwendung eines GIST-Index ist sehr billig. Dies wurde jedoch auf 100 erhöht (durch Vertretung des internen _ST_DWithin).

Ich bin selbst kein großer Fan der CTE-Methode. CTEs sind ein Optimierungszaun. Wenn Sie dies auf diese Weise tun, wird potenzieller Raum für zukünftige Optimierungen beseitigt. Wenn saner Standardeinstellungen das Problem beheben, würde ich lieber ein Upgrade durchführen. Am Ende des Tages müssen wir die Arbeit erledigen und diese Methode funktioniert eindeutig für Sie.

Evan Carroll
quelle
1

Dank des Hinweises von John Powell habe ich die Abfrage überarbeitet, um die County-Begrenzungsbedingung in eine With / CTE-Abfrage zu setzen, und diese verbesserte die Leistung um einiges auf 222 ms gegenüber 700. Immer noch weit entfernt von den 0,74 ms, die ich erhalte, wenn die Daten vorhanden sind eigener Tisch. Ich bin mir immer noch nicht sicher, warum der Planer den Datensatz nicht einschränkt, bevor er eine teure Postgis-Funktion durchläuft, und ich muss es mit größeren Datensätzen versuchen, wenn ich sie habe, aber dies scheint vorerst eine Lösung für diese einzigartige Situation zu sein.

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)
Josh
quelle
1
Wir müssten alle drei Abfragepläne und das Schema für die Tabelle sehen (angefordert in meinem Antwortpunkt).
Evan Carroll
0

Sie sollten einen Index für erstellen zipcode(county, zipcode), der Ihnen einen Index-Scan nur für z geben soll.

Sie können auch mit experimentieren btree_gistErweiterung der Erstellung entweder point(zipcode, geom)Index oder point(geom, zipcode)und zipcode(zipcode, county)Index.

Jakub Kania
quelle