PostGIS距離クエリのインデックスを適切に設定する方法は?


17

から数キロメートル離れRecordたテーブル内のすべてを照会して返すことになっているアプリケーションを構築しています。およびの位置は、Google Geocode APIによって提供される情報から決定されます。XPointXRecordsPointX(long/lat)

私はPostGISが初めてです。簡単な調査の後、私はこの質問を見つけました。答えは次のようなものに沿っているようです:

SELECT *
FROM your_table
WHERE ST_Distance_Sphere(the_geom, ST_MakePoint(your_lon,your_lat)) <= radius_mi * 1609.34

問題は、GISを始めたばかりであるにもかかわらず、上記のクエリを見ると、インデックスをどのように使用できるか想像できないことです。2つの関数呼び出しがあります。毎回テーブルがスキャンされると想像しますRecord。私は間違っていたいです:)

質問:PostGISには、上記のクエリを実行できるインデックスタイプがありますか?そうでない場合、必要なことを行う推奨アプローチは何ですか?


必ず、地理へのキャストで正しいインデックスを作成し、クエリの地理にキャストする前にを適用しST_SetSRID()ST_MakePointください。
ビンス

回答:


37

geometryWGS 1984地理データ(SRID 4326)を使用した列を持つ大きなテーブルで測地クエリのパフォーマンスを向上させるには、2つのキーがあります。

  1. 使用ST_DWithin可能な空間インデックスを使用して検索し、デカルト距離の地理的特徴を見つける関数を使用します
  2. 地理キャストに追加のインデックスを作成して、ST_DWithin使用できるようにします

それでは、現実の世界で何が起こるか見てみましょう。最初に、100万個のランダムポイントのテーブルを作成して設定する必要があります。

DROP TABLE IF EXISTS example1
;

CREATE TABLE example1 (
    idcol   serial      NOT NULL,
    geomcol geometry        NULL,
    CONSTRAINT  example1_pk PRIMARY KEY (idcol),
    CONSTRAINT  enforce_srid CHECK (st_srid(geomcol) = 4326)
)
with (
    OIDS=FALSE
);

INSERT INTO example1(geomcol)
SELECT  ST_SetSRID(
            ST_MakePoint(
            (random()*360.0) - 180.0,
            (acos(1.0 - 2.0 * random()) * 2.0 - pi()) * 90.0 / pi()),
            4326) as geomcol
FROM  generate_series(1, 1000000) vtab;

CREATE INDEX example1_spx ON example1 USING GIST (geomcol);
-- (took about 22 sec)

ST_Distanceクエリを実行すると、予想される全テーブルスキャンが取得されます。

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_Distance(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography) < 30 * 1609.34
;

Aggregate  (cost=274167.33..274167.34 rows=1 width=0) (actual time=4940.531..4940.532 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..273334.00 rows=333333 width=0) (actual time=592.766..4940.509 rows=11 loops=1)
        Output: idcol, geomcol
        Filter: (_st_distance((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography, 0::double precision, true) < 48280.2::double precision)
        Rows Removed by Filter: 999989
Planning time: 2.137 ms
Execution time: 4940.568 ms

ここで、を使用するとST_DWithinまだ完全なテーブルスキャン得られます(高速なスキャンではありますが)。

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=405867.33..405867.34 rows=1 width=0) (actual time=908.716..908.716 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..405834.00 rows=13333 width=0) (actual time=38.449..908.700 rows=7 loops=1)
        Output: idcol, geomcol
        Filter: (((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography) AND ('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision) (...)
        Rows Removed by Filter: 999993
Planning time: 2.017 ms
Execution time: 908.763 ms

そして、これが最後のピースです。カバーリングインデックスの構築(キャストジオグラフィ):

CREATE INDEX example1_gpx ON example1 USING GIST (geography(geomcol));
-- (Takes an extra 13 sec)

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=96538.95..96538.96 rows=1 width=0) (actual time=0.775..0.775 rows=1 loops=1)
  Output: count(*)
  ->  Bitmap Heap Scan on bob.example1  (cost=8671.62..96505.62 rows=13333 width=0) (actual time=0.586..0.769 rows=19 loops=1)
        Output: idcol, geomcol
        Recheck Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
        Filter: (('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision)) AND _st_dwithin((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740':: (...)
        Rows Removed by Filter: 14
        Heap Blocks: exact=33
        ->  Bitmap Index Scan on example1_gpx  (cost=0.00..8668.29 rows=200000 width=0) (actual time=0.384..0.384 rows=33 loops=1)
              Index Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
Planning time: 2.572 ms
Execution time: 0.820 ms

最後に、オプティマイザーは空間インデックスを使用していますが、それは示していますが、友人同士で3桁の違いはありますか?

いくつかの注意事項:

  • 私はデータベースオタクなので、自宅のPCには16Gb RAM、6つの3.3Ghzコア、およびデータベースのデフォルトのテーブルスペース用の256Gb SSDがあります。あなたのマイレージは異なる場合があります

  • 各クエリの前に作成SQLを再実行し、キャッシュ内の「ホット」ページに関してプレイフィールドを平準化しましたが、異なる実行に同じランダムシードが使用されなかったため、結果がわずかに異なる可能性があります

そしてメモ:

  • 元の{-90、+ 90}緯度範囲を微調整して、等面積分布にアークコサインを使用しました(極への偏りが少ない)

1
これは、私がStackexchangeコミュニティで得た最高の回答の1つです。私はまだそれを試みませんでしたが、あなたは私が完全に理解できる完全な例を提供しました。@Vince、ありがとうございました。
-andrerpena

1
geomcolを地理として保存しない理由はありますか?ST_DistanceとST_DWithinは両方とも、地理を想定しています。そして、そうすれば、ジオメトリにジオメトリをキャストする余分なインデックスは必要ありません。
アンドレペナ

これは別の質問であり、質問された場合、意見に基づいて終了する場合があります。
ビンス

1
グーグルでこの結果に出くわし、あなたの答えを@Vinceに感謝します。強制的にgeograhpyにGEOMポイントをキャストの最小の違いではなく、10ミリ秒、平均で43秒からの私の質問時間がかかった...
怒っている84

素晴らしい投稿ですが、 `(acos(1.0-2 * random())* 180.0)/ pi())`は正しくないと思います。範囲は
-90〜90で
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.