Limiter les lignes grâce à la fonction spatiale


9

J'essaie d'améliorer les performances de la requête ci-dessous. Peu importe comment j'écris la requête (sous-requête dans la clause FROM, sous-requête dans la clause WHERE) postgres insiste pour exécuter toutes les ~ 570K lignes via la fonction coûteuse ST_DWITHIN même s'il n'y a que 60 lignes où county = 24. Comment puis-je faire filtrer les postgres sur comté = 24 AVANT de parcourir la fonction postgis qui me semble beaucoup plus rapide et beaucoup plus efficace? 700 ms n'est pas trop préoccupant, mais à mesure que ce tableau atteint 10M +, je suis préoccupé par les performances.

À noter également, p.id est une clé primaire, p.zipcode est un index fk, z.county est un index fk et p.geom a un index GiST.

Requete:

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
    )

EXPLIQUEZ L'ANALYSE:

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

Essayez peut-être de changer la ligne "pointer comme p jointure gauche code postal comme z" en quelque chose comme "pointer comme p jointure gauche (SELECT * FROM zipcode OERE zipcode.county = 24) comme z"?
weiji14

Je viens de l'essayer, mêmes résultats. Lorsque je copie les ~ 60 pointlignes où county = 24 dans une nouvelle table, la requête ne prend que 0,453 ms par rapport à 724, il y a donc certainement une grande différence.
Josh

1
Vous devez utiliser count(*)comme une question de style. Si idc'est un pkid comme vous le dites, c'est ce NOT NULLqui signifie qu'ils sont les mêmes. Sauf count(id)a l'inconvénient que vous devez poser cette question si idest annulable.
Evan Carroll

1
Puis-je demander pourquoi vous utilisez une jointure externe gauche? Essayez de le changer en jointure interne ... Les résultats devraient être identiques
MickyT

Si z.country est le facteur limitant, je vous suggère de le placer d'abord dans une requête CTE, puis de vérifier ces résultats pour une intersection avec votre point d'intérêt. Comme l'indice spatial est probablement moins sélectif que comté = 24 dans ce cas, il ne fait que gêner.
John Powell

Réponses:


3

Vous pouvez voir le problème avec le nombre de lignes attendu et réel. Le planificateur pense qu'il y a 7 437 lignes, mais il n'y en a que 63. Les statistiques sont fausses. Chose intéressante aussi, il n'utilise pas de recherche d'index (index) de boîte englobante avec laquelle DWithinvous pouvez coller le résultat \d point. Quelle version de PostGIS et PostgreSQL?

Essayez de courir ANALYZE point. Obtenez-vous le même plan lorsque vous déplacez la condition vers le haut?

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

J'ai effectué une analyse et j'ai également essayé la nouvelle condition ET en ON, mais j'obtenais toujours des durées de fonctionnement de 700 ms. Il s'agit de PGSQL 9.4 et PostGIS 2.2.
Josh

2

En remarque, il y a une chance raisonnable que ce comportement soit modifié dans PostGIS 2.3.0 si vous voulez l'appeler bogue.

À partir des documents sur PostgreSQL

Un nombre positif donnant le coût d'exécution estimé pour la fonction, en unités de cpu_operator_cost. Si la fonction renvoie un ensemble, il s'agit du coût par ligne retournée. Si le coût n'est pas spécifié, 1 unité est supposée pour le langage C et les fonctions internes, et 100 unités pour les fonctions dans toutes les autres langues. Des valeurs plus élevées amènent le planificateur à essayer d'éviter d'évaluer la fonction plus souvent que nécessaire.

Le coût par défaut était donc de 1 (très bon marché). D_Withinl'utilisation d'un indice GIST est très bon marché. Mais, cela a été porté à 100 (par procuration de l'interne _ST_DWithin).

Je ne suis pas moi-même un grand fan de la méthode CTE. Les CTE sont une barrière d'optimisation. Donc, faire cela de cette façon élimine une certaine possibilité d'optimisation future. Si les paramètres par défaut de saner le corrigent, je préfère mettre à niveau. À la fin de la journée, nous devons faire le travail et cette méthode fonctionne clairement pour vous.


1

Grâce à l'indice de John Powell, j'ai révisé la requête pour mettre la condition limitante du comté dans une requête avec / CTE et cette performance améliorée un peu à 222 ms contre 700. Encore loin des 0,74 ms que j'obtiens lorsque les données sont dans leur propre table. Je ne sais toujours pas pourquoi le planificateur ne limite pas l'ensemble de données avant d'exécuter une fonction postgis coûteuse, et je devrai essayer avec de plus grands ensembles de données lorsque je les aurai, mais cela semble être une solution à cette situation unique pour l'instant.

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
Nous aurions à voir les trois plans de requête et le schéma de la table (demandé dans ma réponse \ d point).
Evan Carroll,

0

Vous devez créer un index sur zipcode(county, zipcode), qui devrait vous donner un scan d'index uniquement sur z.

Vous pouvez également vouloir expérimenter avec l' btree_gistextension en créant un point(zipcode, geom)index ou point(geom, zipcode)et un zipcode(zipcode, county)index.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.