วิธีการตั้งค่าดัชนีสำหรับเคียวรีระยะทาง PostGIS อย่างถูกต้องเป็นอย่างไร


18

ฉันกำลังสร้างโปรแกรมประยุกต์ที่ควรจะแบบสอบถามและกลับทุกRecordในตารางที่เป็นแล้วกิโลเมตรห่างจากX และตำแหน่งของจะถูกกำหนดจากข้อมูลที่จัดทำโดย Google Geocode APIPointXRecordsPointX(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คัดลอกก่อนไปยังภูมิศาสตร์ในแบบสอบถาม
วินซ์

คำตอบ:


38

มีสองปุ่มเพื่อรับประสิทธิภาพการสืบค้น Geodetic ที่ดีกับตารางขนาดใหญ่ที่มีgeometryคอลัมน์โดยใช้ข้อมูลทางภูมิศาสตร์ WGS 1984 (SRID 4326):

  1. ใช้ST_DWithinฟังก์ชั่นซึ่งค้นหาโดยใช้ดัชนีเชิงพื้นที่ที่มีอยู่และจะค้นหาคุณสมบัติทางภูมิศาสตร์ด้วยระยะทางคาร์ทีเซียน
  2. สร้างดัชนีพิเศษในการคัดเลือกนักภูมิศาสตร์เพื่อST_DWithinใช้งานได้

ลองดูสิ่งที่เกิดขึ้นในโลกแห่งความจริง ก่อนอื่นเราต้องสร้างและเติมตารางคะแนนสุ่มหนึ่งล้านจุด:

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

สุดท้ายเครื่องมือเพิ่มประสิทธิภาพกำลังใช้ดัชนีเชิงพื้นที่และมันแสดง แต่ขนาดของคำสั่งสามคำระหว่างเพื่อนคืออะไร

คำเตือนบางอย่าง:

  • ฉันเป็นฐานข้อมูล nerd ดังนั้นพีซีที่บ้านของฉันจึงมี RAM 16Gb หกแกน 3.3Ghz หกและ 256Gb SSD สำหรับพื้นที่เริ่มต้นของฐานข้อมูล ไมล์สะสมของคุณอาจแตกต่างกันไป

  • ฉันเรียกใช้การสร้าง SQL ใหม่อีกครั้งก่อนการสืบค้นแต่ละครั้งเพื่อปรับระดับการเล่นที่เกี่ยวข้องกับหน้า "ร้อน" ในแคช แต่สิ่งนี้อาจให้ผลลัพธ์ที่แตกต่างกันเล็กน้อยเนื่องจากการสุ่มเมล็ดเดียวกันไม่ได้ใช้สำหรับการทำงานที่แตกต่างกัน

และหมายเหตุ:

  • ฉัน tweaked ช่วงละติจูดดั้งเดิม {-90, + 90} เพื่อใช้ arc-cosine สำหรับการกระจายพื้นที่เท่ากัน (เอนเอียงน้อยลงไปที่ขั้ว)

1
นี่เป็นหนึ่งในคำตอบที่ดีที่สุดที่ฉันเคยได้รับในชุมชน Stackexchange ฉันยังไม่ได้ลอง แต่คุณได้ยกตัวอย่างที่ฉันสามารถเข้าใจได้อย่างสมบูรณ์ ขอบคุณมาก @Vince
andrerpena

1
มีเหตุผลใดที่จะไม่เก็บ geomcol เป็นภูมิศาสตร์? ทั้ง ST_ ระยะทางและ ST_D ภายในระยะเวลาที่กำหนด และถ้าเราทำเช่นนั้นเราไม่จำเป็นต้องใช้เรขาคณิตในการคัดเลือกดัชนีเพื่อภูมิศาสตร์
andrerpena

นี่เป็นคำถามที่แตกต่างกันและหากถูกถามอาจถูกปิดตามความคิดเห็น
วินซ์

1
มาถึงผลลัพธ์นี้ใน google และขอขอบคุณ @Vince สำหรับคำตอบของคุณ ความแตกต่างที่เล็กที่สุดของการชี้ตำแหน่งทางภูมิศาสตร์อย่างแรงไปยัง geograhpy ทำให้ฉันใช้เวลาค้นหาจาก 43 วินาทีโดยเฉลี่ย 10 มิลลิวินาทีแทน ..
Angry 84

โพสต์ที่ยอดเยี่ยม แต่ฉันคิดว่า `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` ไม่ถูกต้อง ช่วงไม่ได้อยู่ระหว่าง -90 ถึง 90
hxd1011
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.