Batasi Baris Melalui Fungsi Spasial

9

Saya mencoba meningkatkan kinerja untuk kueri di bawah ini. Tidak peduli bagaimana saya menulis kueri (subquery dalam klausa FROM, subquery dalam klausa WHERE) postgres bersikeras menjalankan semua ~ 570K baris melalui fungsi ST_DWITHIN yang mahal walaupun hanya ada 60 baris di mana county = 24. Bagaimana saya bisa mendapatkan postgres untuk difilter pada county = 24 SEBELUM menjalankan postgis func yang menurut saya akan jauh lebih cepat dan jauh lebih efisien? 700ms bukan penyebab terlalu banyak kekhawatiran, tetapi karena tabel ini tumbuh hingga 10 juta + saya khawatir tentang kinerja.

Juga untuk dicatat, p.id adalah kunci utama, p.zipcode adalah indeks fk, z.county adalah indeks fk, dan p.geom memiliki indeks GiST.

Pertanyaan:

EXPLAIN ANALYZE
  SELECT count(p.id)
  FROM point AS p
  LEFT JOIN zipcode AS z
    ON p.zipcode = z.zipcode
  WHERE z.county = 24
    AND ST_DWithin(
      p.geom, 
      ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269), 
      16090.0,
      false
    )

MENJELASKAN ANALISIS:

Aggregate  (cost=250851.91..250851.92 rows=1 width=4) (actual time=724.007..724.007 rows=1 loops=1)
  ->  Hash Join  (cost=152.05..250851.34 rows=228 width=4) (actual time=0.359..723.996 rows=51 loops=1)
        Hash Cond: ((p.zipcode)::text = (z.zipcode)::text)
        ->  Seq Scan on point p  (cost=0.00..250669.12 rows=7437 width=10) (actual time=0.258..723.867 rows=63 loops=1)
              Filter: (((geom)::geography && '0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography) AND ('0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography && _st_expand((geom)::geography, 16090::double precision)) AND _st_dwithin((g (...)
              Rows Removed by Filter: 557731
        ->  Hash  (cost=151.38..151.38 rows=54 width=6) (actual time=0.095..0.095 rows=54 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 3kB
              ->  Bitmap Heap Scan on zipcode z  (cost=4.70..151.38 rows=54 width=6) (actual time=0.023..0.079 rows=54 loops=1)
                    Recheck Cond: (county = 24)
                    Heap Blocks: exact=39
                    ->  Bitmap Index Scan on fki_zipcode_county_foreign_key  (cost=0.00..4.68 rows=54 width=0) (actual time=0.016..0.016 rows=54 loops=1)
                          Index Cond: (county = 24)
Planning time: 0.504 ms
Execution time: 724.064 ms
Josh
sumber
Mungkin mencoba mengubah garis "titik sebagai p kiri gabung kode pos sebagai z" menjadi sesuatu seperti "titik sebagai p kiri gabung (PILIH * DARI kode pos WHERE zipcode.county = 24) sebagai z"?
weiji14
Baru saja mencobanya, hasilnya sama. Ketika saya menyalin pointbaris ~ 60 di mana county = 24 ke tabel baru sendiri, permintaan hanya membutuhkan 0,453ms dibandingkan dengan 724 sehingga pasti ada perbedaan besar.
Josh
1
Anda harus menggunakan count(*)gaya. Jika idpkid seperti yang Anda katakan, itu NOT NULLartinya mereka sama. Kecuali count(id)memiliki kelemahan yang harus Anda ajukan pertanyaan itu jika tidak iddapat dibatalkan.
Evan Carroll
1
Bisakah saya bertanya mengapa Anda menggunakan gabungan luar kiri? Coba ubah menjadi gabungan dalam ... Hasilnya harus sama
MickyT
Jika z.country adalah faktor pembatas, saya sarankan Anda menempatkan ini terlebih dahulu dalam kueri CTE dan kemudian hanya memeriksa hasil tersebut untuk persimpangan dengan tempat tujuan Anda. Karena indeks spasial mungkin kurang selektif daripada county = 24 dalam kasus ini, itu hanya menghalangi.
John Powell

Jawaban:

3

Anda dapat melihat masalah dengan jumlah baris yang diharapkan vs jumlah aktual. Perencana berpikir bahwa ada 7.437 baris, tetapi hanya ada 63. Statistik tidak aktif. Cukup menarik juga, itu tidak menggunakan indeks kotak terikat (indeks) pencarian dengan DWithinAnda dapat menempelkan hasilnya \d point. Versi PostGIS dan PostgreSQL apa?

Coba jalankan ANALYZE point. Apakah Anda mendapatkan rencana yang sama saat Anda menaikkan kondisinya?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24
Evan Carroll
sumber
Saya menjalankan analisis dan juga mencoba kondisi AND baru dalam ON tetapi masih mendapatkan 700ms kali menjalankan. Ini adalah PGSQL 9.4 dan PostGIS 2.2.
Josh
2

Sebagai catatan tambahan, ada kemungkinan yang masuk akal bahwa perilaku ini dimodifikasi di PostGIS 2.3.0 jika Anda ingin menyebutnya bug.

Dari dokumen di PostgreSQL

Angka positif yang memberikan perkiraan biaya eksekusi untuk fungsi, dalam satuan cpu_operator_cost. Jika fungsi mengembalikan satu set, ini adalah biaya per baris yang dikembalikan. Jika biaya tidak ditentukan, 1 unit diasumsikan untuk bahasa-C dan fungsi internal, dan 100 unit untuk fungsi dalam semua bahasa lainnya. Nilai yang lebih besar menyebabkan perencana untuk mencoba menghindari mengevaluasi fungsi lebih sering daripada yang diperlukan.

Jadi biaya standarnya adalah 1 (sangat murah). D_Withinmenggunakan indeks GIST sangat murah. Tapi, itu meningkat menjadi 100 (dengan proxy internal _ST_DWithin).

Saya sendiri bukan penggemar berat metode CTE. CTE adalah pagar optimasi. Jadi melakukan ini sedemikian rupa menghilangkan beberapa ruang potensial untuk optimasi di masa depan. Jika standar saner memperbaikinya, saya lebih suka memutakhirkan. Pada akhirnya, kami harus menyelesaikan pekerjaan dan metode itu jelas bekerja untuk Anda.

Evan Carroll
sumber
1

Berkat petunjuk John Powell, saya merevisi kueri untuk menempatkan kondisi pembatasan daerah dalam kueri with / CTE dan kinerja yang ditingkatkan ini sedikit ke 222ms vs 700. Masih jauh sekali dari 0,74 ms yang saya dapatkan ketika data ada di dalam meja sendiri. Saya masih tidak yakin mengapa perencana tidak membatasi set data sebelum menjalankan melalui fungsi postgis yang mahal, dan saya harus mencoba dengan dataset yang lebih besar ketika saya memilikinya tetapi ini tampaknya menjadi solusi untuk situasi unik ini untuk saat ini.

with points as (
   select p.id, p.geom from point p inner join zipcode z
   on p.zipcode = z.zipcode
   where county = 24
   ) 


SELECT count(points.id)
FROM points
WHERE ST_DWITHIN(points.geom, (ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269)), 16090.0, false)
Josh
sumber
1
Kami harus melihat ketiga paket kueri, dan skema untuk tabel (diminta dalam jawaban saya \ d point).
Evan Carroll
0

Anda harus membuat indeks aktif zipcode(county, zipcode), yang akan memberi Anda hanya memindai indeks pada z.

Anda mungkin juga ingin bereksperimen dengan btree_gistekstensi yang membuat point(zipcode, geom)indeks atau point(geom, zipcode)dan zipcode(zipcode, county)indeks.

Jakub Kania
sumber