Postgisの内部結合でインデックスを使用する方法は?


8

2つの異なるテーブルで2セットのポイントを取得しました。Table_aは100kポイント、table_bは300kポイントを獲得しました。関係で最も近いポイントを見つけようとすると、tabla_aから50メートル以内にあるtable_bからポイントが見つかります。その秋の列を計算した後、それらをtable_a a_id列でグループ化し、最高値を返します。

私はこの批評家を満たす次のクエリを書きました

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;

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);

しかし、私の問題は、それらを使用するためのクエリを作成できないことです。クエリプランナーは、遅いシーケンススキャンを選択し続けています。_st_3ddwithinst_3ddwithin<<->> <50で変更するテストを実行し、50 mのバッファーと交差を作成します。st_3ddistance<50ですが、プランナーがシーケンススキャンを選択するたびに、より高いパフォーマンスでインデックスを使用する方法、またはインデックスを使用するようにクエリを変更する方法はありますか?

私のクエリプラン:

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)

1
クエリプランにはあるがSQLではないe_wires_mv12404とは正確には何ですか?内部クエリだけのクエリプランはどのようなものですか?_STで始まる関数は使用しないことをお勧めします。最後に、2DでST_DWithinを使用して35メートルを使用すると、パフォーマンスが向上する可能性があります。これは、キューブの反対側のエッジから50メートルとほぼ同じです。50メートル以内の最も近い1つのポイントを探しているので、これは横方向の結合に適しており、ORDER BY a.geom <-> b.geomコンストラクトを使用するのに適しています。
ジョンパウエル

1
昨年も同様の問題が発生しました。この投稿をあなたのために掘り下げました。質問に答えられない場合はお知らせください。
WxGeo

2
関数のSQL定義を見ると、st_dwithinのようなst_関数は実際には境界ボックスのチェックであり、st関数の呼び出しであることがわかります。st関数を直接呼び出すときにインデックスを使用できるのは、境界ボックス部分であり、データベースがインデックスを使用する方法はありません。再チェック関数を直接呼び出します。
NicklasAvén18年

1
もし私のような参加横ソリューションを書くことだろう、私はそれはあなたが記述何のために働くと思う
ジョン・パウエル

1
で始まる@AndreSilva関数_STは、インデックスでフィルタリングした後にPostGISによって呼び出される内部関数です。それらを直接呼び出す場合、インデックスは使用されません。
dbaston 2018

回答:


6

まず、コメントで述べたように、ST関数の前のアンダースコア(_ST_3DWithin)を使用すると、インデックスが使用されなくなります。私はこれについての最近の言及を見つけることができませんが 、たとえば_ST_Intersectsを検索すると、古いドキュメントでは次のように示されます。

インデックスの使用を回避するには、_ST_Intersects関数を使用します。

編集:コメントの@dbastonで明らかにされているように、アンダースコアが先行する関数は、呼び出されたときにインデックスを使用しない内部関数であり、これは引き続き当てはまります(ドキュメントでは見つけるのは難しいですが)。

クエリはLATERAL JOIN構文の恩恵を受ける可能性があります。この構文は、このようなk最近隣(kNN)問題に適しています。

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;

これにより、テーブルa(この場合はLIMIT 1のため1)からテーブルbまでの最も近いkジオメトリを、それらの間の3D距離順に並べて見つけることができます。表aには、表bから50メートル以内にないジオメトリがある可能性があるため、LEFT JOINを使用して記述されています。

ラテラルクエリを使用すると、前のFROM句の列を参照できるため、標準のサブクエリよりも強力になります。ドキュメントを参照してください。

データに対してこれをテストすることはできませんが、同様のクエリを実行すると、EXPLAINステートメントは適切なインデックスの使用を示します。


あなたのコメントは素晴らしい意味を成しますが、あなたが提供したクエリは元のクエリとは異なる考えをしているため、私は答えを受け入れることができません。以前の邸宅のように、私は単一の最も近いポイントではなく、50メートル以内のポイントのグループを探しています。次に、a_idでグループ化された最高の減算値(height-ST_3Ddistance(geom、a.geom))を選択しています
Losbaltica

クエリを変更しました。必要に応じて確認し、改善を加えてください:)
Losbaltica

1
クエリを変更しましたが、欠けているのは「高さ-」だけでした。これにより、50以内のすべてのポイントが検出され、高さが最も高いポイント-ST_3Ddistance(b.geom、a.geom)の値が返されます。これはすべて各ラテラルクエリとLIMIT 1によって処理されるため、明確にオンにする必要はありません。つまり、各a_idの最大のフォール値のみを取得します。
ジョンパウエル

これは、当初期待したとおりに機能していますか。EXPLAINは賢明に見えますか?
ジョンパウエル

期待どおりに動作しています。クエリのパフォーマンスはほぼ同じですが、クエリのコストははるかに小さくなります。新しいEXPLAIN: Explain.depesz.com/s/Js5G クエリの最適化の限界に達したと思います。サーバーを調整するか、テーブル/ロジックをリファクタリングすることしかできないと思います。だから私は元の質問に答えています
Losbaltica '11 / 07/18

2

このPostGISドキュメントへのリンクでは、インデックスとクエリプランナーが確実に最適化されるように、次の手順を推奨しています。

  1. インデックスの使用に関する決定を行うためのより良い情報をクエリプランナーに提供するために、テーブル内の値の数と分布に関する統計が収集されていることを確認してください。VACUUM ANALYZEは両方を計算します。

  2. バキュームが役に立たない場合は、set enable_seqscan to offを使用して、プランナーに一時的にインデックス情報を使用させることができます。コマンド。このようにして、プランナがクエリのインデックス高速化クエリプランを生成できるかどうかを確認できます。このコマンドはデバッグにのみ使用する必要があります。一般的に言えば、プランナーは、インデックスをいつ使用するかよりもよく知っています。クエリを実行したら、ENABLE_SEQSCANをオンに戻すことを忘れないでください。そうすれば、他のクエリが通常どおりプランナを利用できます。

  3. enable_seqscanをオフに設定した場合。クエリの実行に役立ちますが、Postgresはハードウェア用に調整されていない可能性があります。順次スキャンとインデックススキャンのコストについてプランナーが間違っている場合は、postgresql.confのrandom_page_costの値を減らすか、またはset random_page_costを1.1;にしてみてください。パラメータのデフォルト値は4です。1(SSD)または2(高速磁気ディスク)に設定してみてください。値を小さくすると、プランナはインデックススキャンを使用する傾向が強くなります。

  4. enable_seqscanをオフに設定した場合。あなたのクエリを助けません、あなたがPostgresがまだもつれを解くことができない構造を使うことが起こるかもしれません。インライン選択のサブクエリは1つの例です。プランナが最適化できるように、たとえばLATERAL JOINに書き換える必要があります。

したがって、インデックスを使用するようにクエリを書き換える前に、まずステップ1〜3を試してください。それが機能しない場合は、クエリを変更してみてください。

私は(コードを実行せずにSQLを作成できる限り)以下のクエリは同じ結果を返すと信じていますが、より効率的かどうかはわかりません。

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;

他のコメントのように_st_3ddwithinをst_dwithinに変更し、その後VACUUM ANALYZEを実行した後、非常に興味深いことに、プランナがついにインデックスをキャッチし始めます!
Losbaltica

0

Postgres 10(またはそれ以降)を使用している場合は、データを並列テーブルにロードすることを強くお勧めします。

おそらくそれをチューニングする時間を費やす必要があります(データのパーティション化とワーカーの数)が、努力する価値があると思います。理論的には、KNNは高度に並列化可能であり、ワーカーの量がKNN演算が計算される要素の数と等しい場合、O(1)であっても一定の時間の複雑さに達します。

ここでは、データのロードとクエリの実行に関する実用的なリファレンスを提供します。彼は計画調整(より多くの労働者に行動を強制するため)の詳細をここに提供します。並列スクリプトは多くのタスク調整を伴うため、最も極端な並列化を提供するという極端な理論的限界は、ネットワークやその他のシステム設計の特性により、実際には成り立ちません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.