Bagaimana cara mengatur indeks untuk kueri jarak PostGIS dengan benar?

17

Saya sedang membangun aplikasi yang seharusnya meminta dan mengembalikan setiap Recorddalam tabel yang berjarak beberapa Xkilometer dari PointX. Recordsdan PointXposisi ditentukan dari (long/lat)informasi yang disediakan oleh Google Geocode API.

Saya baru mengenal PostGIS. Setelah penelitian singkat, saya menemukan pertanyaan ini . Jawabannya sepertinya sejalan:

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

Masalahnya adalah: Meskipun saya baru memulai di GIS, ketika saya melihat kueri di atas, saya tidak bisa membayangkan bagaimana ini bisa menggunakan indeks. Ada 2 panggilan fungsi. Saya membayangkan tabel dipindai untuk setiap Record. Saya ingin salah :)

Pertanyaan: Apakah PostGIS memiliki tipe indeks apa pun yang mampu membuat kinerja kueri di atas? Jika tidak, pendekatan apa yang direkomendasikan untuk melakukan apa yang saya butuhkan?

andrerpena
sumber
Pastikan Anda membuat indeks yang tepat, di atas cetakan untuk geografi, dan menerapkan ST_SetSRID()pada ST_MakePointsebelum casting ke geografi dalam kueri.
Vince

Jawaban:

37

Ada dua kunci untuk mendapatkan kinerja kueri geodetik yang baik dengan tabel besar dengan geometrykolom menggunakan data geografis WGS 1984 (SRID 4326):

  1. Gunakan ST_DWithinfungsi, yang mencari menggunakan indeks spasial yang tersedia, dan akan menemukan fitur geografi dengan jarak Cartesian
  2. Bangun indeks tambahan pada pemeran geografi, jadi ST_DWithin bisa menggunakannya

Jadi mari kita lihat apa yang terjadi di dunia nyata. Pertama, kita perlu membuat dan mengisi tabel satu juta poin acak:

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)

Jika kami menjalankan kueri ST_Distance, kami mendapatkan pemindaian tabel lengkap yang diharapkan:

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

Sekarang, jika kita gunakan ST_DWithin, kita masih mendapatkan pemindaian tabel penuh (meskipun lebih cepat):

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

Dan ini adalah bagian terakhir - Membangun indeks penutup (cast geography):

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

Akhirnya, pengoptimal menggunakan indeks spasial, dan itu menunjukkan, tapi apa tiga urutan besarnya antara teman?

Beberapa peringatan:

  • Saya seorang kutu buku basis data, jadi PC rumahan saya telah mendapat RAM 16Gb, enam core 3.3Ghz, dan SSD 256Gb untuk tablespace default basis data; jarak tempuh Anda dapat bervariasi

  • Saya menjalankan ulang pembuatan SQL sebelum setiap permintaan, untuk meratakan lapangan bermain sehubungan dengan halaman "panas" dalam cache, tetapi ini bisa menghasilkan hasil yang sedikit berbeda karena seed acak yang sama tidak digunakan untuk proses yang berbeda

Dan sebuah catatan:

  • Saya mengubah rentang lintang {-90, + 90} asli untuk menggunakan arc-cosine untuk distribusi area yang sama (kurang bias terhadap kutub)
Vince
sumber
1
Ini adalah salah satu jawaban terbaik yang pernah saya dapatkan di komunitas Stackexchange. Saya masih belum mencobanya tetapi Anda memberikan contoh lengkap yang bisa saya pahami sepenuhnya. Terima kasih banyak @Vince.
andrerpena
1
Apakah ada alasan mengapa tidak menyimpan geomol sebagai geografi? Baik ST_Distance dan ST_DDalam berharap geografi. Dan jika kita melakukannya, kita tidak perlu geometri pengecoran indeks tambahan untuk geografi.
andrerpena
Ini adalah pertanyaan yang berbeda, dan jika ditanyakan, mungkin ditutup sebagai berdasarkan opini.
Vince
1
Datang di hasil ini di google dan terima kasih @Vince atas jawaban Anda. Perbedaan terkecil dari pemaksaan memaksa titik geom ke geograhpy mengambil waktu permintaan saya dari rata-rata 43 detik menjadi 10msec sebagai gantinya ..
Marah 84
posting hebat, tapi saya rasa `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` tidak benar. kisarannya tidak dari -90 hingga 90
hxd1011