PostGIS mesafe sorguları için dizinler nasıl doğru şekilde ayarlanır?


18

Ben sorgu ve kilometre uzakta Recordbir tablodaki her dönmek için gerekiyordu bir uygulama inşa ediyorum . ve konumları Google Geocode API tarafından sağlanan bilgilerden belirlenir .XPointXRecordsPointX(long/lat)

PostGIS'te yeniyim. Hızlı bir araştırmadan sonra bu soruyu buldum . Cevap aşağıdaki gibi görünmektedir:

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

Sorun şu: Ben sadece GIS başlamış olmasına rağmen, yukarıdaki sorguya baktığınızda, muhtemelen bu nasıl bir dizin kullanabilirsiniz düşünemiyorum. 2 işlev çağrısı vardır. Masanın her biri için tarandığını hayal ediyorum Record. Yanlış olmak istiyorum :)

Soru: PostGIS'in yukarıdaki sorgu performansını artırabilecek herhangi bir dizin türü var mı? Değilse, ihtiyacım olanı yapmak için önerilen yaklaşım ne olurdu?


Emin coğrafyaya bir döküm üzerine sağ dizin yaratmak ve uygulamak Make ST_SetSRID()için ST_MakePointsorguda coğrafyaya döküm önce.
Vince

Yanıtlar:


38

geometryWGS 1984 coğrafi verilerini (SRID 4326) kullanan sütunları olan büyük tablolarda iyi bir jeodezik sorgu performansı elde etmenin iki anahtarı vardır :

  1. Kullanılabilir ST_DWithinbir uzamsal dizin kullanarak arama yapan ve Kartezyen mesafeli coğrafya özelliklerini bulan işlevi kullanın
  2. Cast coğrafyası üzerinde ekstra bir dizin oluşturun, böylece ST_DWithinkullanabilirsiniz

Öyleyse gerçek dünyada neler olduğuna bakalım. İlk önce bir milyon rastgele noktadan oluşan bir tablo oluşturmalı ve doldurmalıyız:

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 sorgusunu yürütürsek, beklenen tam tablo taramasını alırız:

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

Kullandığımız Şimdi, eğer ST_DWithinbiz hala (daha hızlı bir de olsa) tam tablo taraması olsun:

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

Ve bu son parça - Kaplama endeksinin oluşturulması (döküm coğrafyası):

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

Son olarak, optimizer uzamsal endeksi kullanıyor ve gösteriyor, ancak arkadaşlar arasındaki üç büyüklük sırası nedir?

Bazı uyarılar:

  • Ben bir veritabanı nerd değilim, bu yüzden ev bilgisayarım 16Gb RAM, altı 3.3Ghz çekirdek ve veritabanı varsayılan tablo alanı için 256Gb SSD var; kilometreniz değişebilir

  • Önbellekteki "sıcak" sayfalara göre oyun alanını düzeltmek için her sorgudan önce oluşturma SQL'i yeniden çalıştırdım, ancak aynı rastgele tohum farklı çalışmalar için kullanılmadığı için bu biraz farklı sonuçlar üretebilir

Ve bir not:

  • Eşit alan dağılımı için kutup kosinüsü kullanmak için orijinal {-90, + 90} enlem aralığını değiştirdim (kutuplara daha az önyargılı)

1
Bu Stackexchange topluluğunda şimdiye kadar aldığım en iyi cevaplardan biri. Hala denemedim ama tamamen anlayabildiğim tam bir örnek verdin. @Vince çok teşekkür ederim.
andrerpena

1
Geomcol'u coğrafya olarak saklamamanın bir nedeni var mı? Hem ST_Distance hem de ST_DBurada coğrafya bekleniyor. Ve eğer böyle yaparsak, coğrafya için ekstra indeks döküm geometrisine ihtiyacımız olmazdı.
andrerpena

Bu farklı bir sorudur ve istenirse görüşe dayalı olarak kapatılabilir.
Vince

1
Google'da bu sonuca rastladı ve cevabınız için @Vince teşekkür ederiz. Coğrafi bir noktayı zorla bir geograhpy'ye dökmenin en küçük farkı, sorgu zamanımı ortalama 43 saniyeden 10msec'e çıkardı ..
Angry 84

büyük yazı, ama bence `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` doğru değil. aralık
-90'dan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.