Comment utiliser les index avec jointure interne dans Postgis?

8

J'ai obtenu 2 ensembles de points dans 2 tableaux distincts. Table_a a obtenu 100 000 points et table_b a obtenu 300 000 points. J'essaie de trouver les points les plus proches par rapport me trouver n'importe quel point de table_b qui est à moins de 50 mètres de tabla_a. Après avoir calculé la colonne d'automne, regroupez-les par colonne table_a a_id et retournez la valeur la plus élevée.

J'ai écrit une requête suivante qui répond à cette criteira

SELECT DISTINCT ON (a_id) *
FROM (
       SELECT
         table_b.b_id,
         table_b.height - st_3ddistance(table_b.geom, table_a.geom) fall,
         table_b.geom,
         table_a.a_id
       FROM table_a
         INNER JOIN table_b ON _st_3ddwithin(table_a.geom, table_b.geom, 50)) a
WHERE fall >= 0
ORDER BY a_id, fall DESC;

J'ai ajouté des index de géométrie 3D:

CREATE INDEX table_a_geom ON table_a USING GIST (geom gist_geometry_ops_nd);
CREATE INDEX table_b_geom ON table_b USING GIST (geom gist_geometry_ops_nd);

Cependant, mon problème est que je ne peux pas faire de requête pour les utiliser. Le planificateur de requêtes continue de choisir un balayage de séquence lent. Je lance un test en changeant _st_3ddwithin avec st_3ddwithin , <<->> <50 , en créant 50 m de tampon et en intersectant , st_3ddistance <50 mais à chaque fois le planificateur choisit le balayage de séquence. Existe-t-il un moyen d'utiliser des index avec des performances supérieures ou de modifier la requête pour utiliser des index?

Mon plan de requête:

Unique  (cost=10462593.70..10473018.43 rows=1 width=144)
  ->  Sort  (cost=10462593.70..10467806.06 rows=2084945 width=144)
        Sort Key: table_a.nmbayuid, ((table_b.height - st_3ddistance(table_b.geomgr, table_a.geom))) DESC
        ->  Nested Loop  (cost=0.00..10243762.28 rows=2084945 width=144)
              Join Filter: (_st_dwithin(table_a.geom, table_b.geomgr, '50'::double precision) AND ((table_b.height - st_3ddistance(table_b.geomgr, table_a.geom)) >= '0'::double precision))
              ->  Seq Scan on table_b  (cost=0.00..1459.47 rows=47147 width=96)
              ->  Materialize  (cost=0.00..10.97 rows=398 width=56)
                    ->  Seq Scan on table_a  (cost=0.00..8.98 rows=398 width=56)
Losbaltica
la source
1
Quel est exactement e_wires_mv12404 qui est dans le plan de requête mais pas le SQL? À quoi ressemble le plan de requête pour la requête interne? Je suggère de ne pas utiliser de fonction commençant par _ST. Enfin, vous pourriez être en mesure d'obtenir de meilleures performances en utilisant ST_DWithin en 2D, en utilisant 35 mètres, ce qui correspond plus ou moins à 50 mètres des bords opposés d'un cube. Comme vous recherchez le seul point le plus proche à moins de 50 mètres, cela pourrait être un bon candidat pour une jonction latérale et l'utilisation de la construction ORDER BY a.geom <-> b.geom.
John Powell
1
J'ai eu un problème similaire l'année dernière, j'ai déterré ce post pour vous , faites le moi savoir s'il ne répond pas à vos questions?
WxGeo
2
Si vous regardez la définition sql des fonctions, vous voyez que les fonctions st_ comme st_dwithin sont en fait une case à cocher et un appel à la fonction st . C'est la partie du cadre englobant qui peut utiliser l'index lorsque vous appelez directement la fonction st , il n'y a aucun moyen pour la base de données d'utiliser l'index. Vous appelez la fonction revérifier directement.
Nicklas Avén
1
Souhaitez-vous que j'écrive la solution de jointure latérale, je pense que cela fonctionnerait bien pour ce que vous décrivez
John Powell
1
Les fonctions @AndreSilva commençant par _STsont des fonctions internes appelées par PostGIS après filtrage avec un index. Si vous les appelez directement, l'index ne sera pas utilisé.
dbaston

Réponses:

6

Premièrement, comme cela a été noté dans les commentaires, le trait de soulignement avant la fonction ST, c'est-à-dire _ST_3DWithin, conduira à l'index non utilisé. Je ne trouve aucune mention récente à ce sujet, mais dans les anciens documents si vous recherchez, par exemple, _ST_Intersects, il indique:

Pour éviter l'utilisation d'index, utilisez la fonction _ST_Intersects.

EDIT: Comme précisé par @dbaston dans les commentaires, les fonctions avec le soulignement de tête sont des fonctions internes qui n'utilisent pas l'index lors de l'appel et cela continue d'être le cas (bien qu'il soit difficile à trouver dans les documents).

Votre requête pourrait éventuellement bénéficier de la syntaxe LATERAL JOIN, qui se prête bien à k problèmes de voisin le plus proche (kNN) comme celui-ci.

SELECT 
   a.a_id, 
   b.b_id
   b.height - ST_3Ddistance(b.geom, a.geom) AS fall,
  FROM table_a a
     LEFT JOIN LATERAL
       (SELECT
            b_id,         
            geom,
            height        
          FROM table_b
          WHERE ST_3Ddwithin(a.geom, geom, 50)
          AND height - ST_3Ddistance(geom, a.geom) > 0
          ORDER BY height - ST_3Ddistance(b.geom, a.geom) DESC 
          LIMIT 1
        ) b ON TRUE;

Cela vous permet de trouver les k géométries les plus proches de la table a (dans ce cas 1, en raison de LIMIT 1) à la table b, ordonnées par la distance 3D entre elles. Il est écrit à l'aide d'un LEFT JOIN, car il est concevable qu'il puisse y avoir des géométries dans le tableau a qui ne se trouvent pas à moins de 50 mètres du tableau b.

Les requêtes latérales vous permettent de référencer des colonnes de la clause FROM précédente, ce qui la rend plus puissante que les sous-requêtes standard, voir la documentation .

Je ne peux pas tester cela par rapport à vos données, mais lorsque j'ai exécuté des requêtes similaires, l'instruction EXPLAIN indique une utilisation correcte de l'index.

John Powell
la source
Vos commentaires sont très sensés, mais je ne peux pas accepter la réponse, car la requête que vous avez fournie est différente de la requête d'origine. Comme je mange avant "je ne cherche pas un seul point le plus proche mais un groupe de points à moins de 50 mètres et ensuite je sélectionne celui avec la valeur de soustraction la plus élevée (hauteur - ST_3Ddistance (geom, a.geom)) groupé par a_id
Losbaltica
J'ai modifié votre requête, veuillez y jeter un œil et ajouter des améliorations si nécessaire :)
Losbaltica
1
J'ai modifié la requête, la seule chose qui manquait était "hauteur -" dans l'ordre par. Cela va maintenant trouver tous les points à moins de 50 et retourner celui avec la valeur la plus élevée - ST_3Ddistance (b.geom, a.geom). Il n'est pas nécessaire d'activer distinct, car tout est géré par chaque requête latérale et LIMIT 1, c'est-à-dire que vous n'obtiendrez que la plus grande valeur de chute pour chaque a_id.
John Powell
Est-ce que cela fonctionne maintenant comme prévu à l'origine? EXPLAIN semble-t-il raisonnable?
John Powell
Fonctionne comme prévu. Les performances des requêtes sont presque les mêmes, mais le coût de la requête est beaucoup plus faible. Nouveau EXPLAIN: Expliquer.depesz.com/s/Js5G Je pense que j'atteins la limite de l'optimisation des requêtes et pense seulement que je peux faire maintenant est de régler le serveur ou de refactoriser les tables / logique. Donc, cela me répond à la question initiale
Losbaltica
2

Ce lien vers la documentation PostGIS recommande les étapes suivantes afin de garantir l'optimisation des index et du planificateur de requêtes:

  1. Assurez-vous que des statistiques sont collectées sur le nombre et la distribution des valeurs dans une table, afin de fournir au planificateur de requêtes de meilleures informations pour prendre des décisions concernant l'utilisation de l'index. VACUUM ANALYZE calculera les deux.

  2. Si le nettoyage par aspiration n'aide pas, vous pouvez temporairement forcer le planificateur à utiliser les informations d'index en utilisant la fonction set enable_seqscan sur off; commander. De cette façon, vous pouvez vérifier si le planificateur est capable de générer un plan de requête accéléré par index pour votre requête. Vous ne devez utiliser cette commande que pour le débogage: de manière générale, le planificateur sait mieux que vous quand utiliser les index. Une fois que vous avez exécuté votre requête, n'oubliez pas de réactiver ENABLE_SEQSCAN, afin que les autres requêtes utilisent normalement le planificateur.

  3. Si désactivez enable_seqscan; aide votre requête à fonctionner, votre Postgres n'est probablement pas réglé pour votre matériel. Si vous trouvez que le planificateur se trompe sur le coût des analyses séquentielles vs index, essayez de réduire la valeur de random_page_cost dans postgresql.conf ou utilisez set random_page_cost à 1.1 ;. La valeur par défaut du paramètre est 4, essayez de le régler sur 1 (sur SSD) ou 2 (sur disques magnétiques rapides). La diminution de la valeur rend le planificateur plus enclin à utiliser les analyses d'index.

  4. Si désactivez enable_seqscan; n'aide pas votre requête, il se peut que vous utilisiez une construction que Postgres n'est pas encore en mesure de démêler. Une sous-requête avec sélection en ligne en est un exemple - vous devez la réécrire dans le planificateur de formulaire pour optimiser, disons, une LATÉRALE JOIN.

Essayez donc d'abord les étapes 1 à 3 avant de réécrire votre requête pour utiliser les indices. Si cela ne fonctionne pas, vous pouvez essayer de modifier la requête.

Je crois (au mieux de ma capacité à fouetter SQL sans exécuter le code) que la requête ci-dessous retournera des résultats identiques aux vôtres, mais je ne sais pas si elle sera plus efficace.

SELECT DISTINCT on (a_id),
    table_b.b_id as b_id,
    table_b.height - st_3ddistance(table_b.geom, table_a.geom) as fall,
    table_b.geom as b_geom,
    table_a.a_id as a_id
    FROM table_a
         INNER JOIN table_b ON _st_3ddwithin(table_a.geom, table_b.geom, 50)) a
WHERE fall >= 0
ORDER BY a_id, fall DESC;
jgm_GIS
la source
Très intéressant après avoir changé _st_3ddwithin en st_dwithin comme d'autres commentaires l'ont suggéré et après avoir exécuté VACUUM ANALYZE, le planificateur commence enfin à attraper l'index!
Losbaltica
0

Si vous utilisez Postgres 10 (ou plus récent), je vous recommande fortement de charger vos données dans des tables parallèles.

Vous aurez probablement besoin de passer du temps à le régler (partitionnement des données et nombre de travailleurs), mais je pense que cela en vaut la peine. Théoriquement, KNN est hautement parallélisable, atteignant des complexités temporelles constantes, même O (1) si le nombre de travailleurs est égal au nombre d'éléments où une opération KNN sera calculée.

Des références pratiques sur le chargement des données et l'exécution des requêtes sont fournies ici . Il fournit des détails sur tunning plan (pour forcer plus de travailleurs à actionnées) ici . Il est important de noter que les scripts parallèles impliquent beaucoup de coordination des tâches, de sorte que la limite théorique extrême de fournir la parallélisation la plus extrême ne tient pas en pratique, en raison de la mise en réseau et d'autres caractéristiques de conception des systèmes.

Ricardo Barros Lourenço
la source