Saya memiliki kueri yang relatif sederhana di atas meja dengan baris 1.5M:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
keluaran:
Limit (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1) -> Bitmap Heap Scan on publication (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1) Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321)) -> BitmapOr (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1) -> Bitmap Index Scan on publication_pkey (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1) Index Cond: (mtid = 9762715) -> Bitmap Index Scan on publication_last_modifier_btree (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1) Index Cond: (last_modifier = 21321) Total runtime: 1.027 ms
Sejauh ini bagus, cepat dan menggunakan indeks yang tersedia.
Sekarang, jika saya memodifikasi sedikit kueri, hasilnya adalah:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
The EXPLAIN ANALYZE
output:
Limit (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1) -> Seq Scan on publication (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1) Filter: ((hashed SubPlan 1) OR (last_modifier = 21321)) SubPlan 1 -> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1) Total runtime: 2841.442 ms
Tidak begitu cepat, dan menggunakan pemindaian seq ...
Tentu saja, kueri asli yang dijalankan oleh aplikasi sedikit lebih kompleks, dan bahkan lebih lambat, dan tentu saja orisinal yang dihasilkan hibernate tidak (SELECT 9762715)
, tetapi kelambatan ada bahkan untuk itu (SELECT 9762715)
! Permintaan dihasilkan oleh hibernate, sehingga cukup sulit untuk mengubahnya, dan beberapa fitur tidak tersedia (mis. UNION
Tidak tersedia, yang akan cepat).
Pertanyaan-pertanyaan
- Mengapa indeks tidak dapat digunakan dalam kasus kedua? Bagaimana mereka bisa digunakan?
- Bisakah saya meningkatkan kinerja permintaan dengan cara lain?
Pikiran tambahan
Tampaknya kita bisa menggunakan kasus pertama dengan melakukan SELECT secara manual, dan kemudian memasukkan daftar yang dihasilkan ke dalam kueri. Bahkan dengan 5000 angka dalam daftar IN () itu empat kali lebih cepat daripada solusi kedua. Namun, sepertinya SALAH (juga, bisa 100 kali lebih cepat :)). Ini benar-benar tidak dapat dimengerti mengapa perencana kueri menggunakan metode yang sama sekali berbeda untuk dua pertanyaan ini, jadi saya ingin mencari solusi yang lebih baik untuk masalah ini.
JOIN
bukanIN ()
? Juga,publication
sudah dianalisis baru-baru ini?(SELECT 9762715)
.(SELECT 9762715)
. Untuk pertanyaan hibernasi: itu bisa dilakukan, tetapi membutuhkan penulisan ulang kode serius, karena kami memiliki kueri kriteria hibernasi yang ditentukan pengguna yang diterjemahkan saat itu juga. Jadi pada dasarnya kami akan memodifikasi hibernate yang merupakan upaya besar dengan banyak efek samping yang mungkin.Jawaban:
Inti dari masalah menjadi jelas di sini:
Postgres memperkirakan untuk mengembalikan 744661 baris, pada kenyataannya, ternyata menjadi satu baris. Jika Postgres tidak tahu apa yang diharapkan dari kueri, Postgres tidak dapat merencanakan dengan lebih baik. Kita perlu melihat permintaan aktual yang tersembunyi di belakang
(SELECT 9762715)
- dan mungkin juga tahu definisi tabel, batasan, kardinalitas, dan distribusi data. Jelas, Postgres tidak dapat memprediksi bagaimana beberapa baris akan dikembalikan oleh itu. Mungkin ada cara untuk menulis ulang query, tergantung pada apa yang .Jika Anda tahu bahwa subquery tidak pernah dapat mengembalikan lebih dari
n
baris, Anda bisa memberi tahu Postgres dengan menggunakan:Jika n cukup kecil, Postgres akan beralih ke scan indeks (bitmap). Namun , itu hanya berfungsi untuk kasus sederhana. Berhenti berfungsi saat menambahkan
OR
kondisi: perencana kueri saat ini tidak dapat mengatasinya.Saya jarang menggunakannya
IN (SELECT ...)
untuk memulai. Biasanya ada cara yang lebih baik untuk mengimplementasikan hal yang sama, seringkali denganEXISTS
semi-join. Terkadang dengan (LEFT
)JOIN
(LATERAL
) ...Solusi yang jelas akan digunakan
UNION
, tetapi Anda mengesampingkan itu. Saya tidak bisa mengatakan lebih banyak tanpa mengetahui subquery yang sebenarnya dan detail relevan lainnya.sumber
(SELECT 9762715)
! Jika saya menjalankan kueri persis yang Anda lihat di atas. Tentu saja, permintaan hibernate asli sedikit lebih rumit, tetapi saya (pikir saya) berhasil menentukan di mana perencana kueri tersesat, jadi saya menyajikan bagian dari permintaan itu. Namun, penjelasan dan kueri di atas adalah kata demi kata ctrl-cv.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
juga melakukan pemindaian sekuensial dan juga berjalan sekitar 3 detik ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. Dan efeknya masih ada untuk pertanyaan yang samatest
: setiap subquery menghasilkan pemindaian seq ... Saya mencoba keduanya 9.1 dan 9.4. Efeknya sama.OR
syarat. Trik denganLIMIT
hanya berfungsi untuk case yang lebih sederhana.Rekan saya telah menemukan cara untuk mengubah kueri sehingga perlu menulis ulang sederhana dan melakukan apa yang perlu dilakukan, yaitu melakukan subselect dalam satu langkah, dan kemudian melakukan operasi lebih lanjut pada hasilnya:
Analisis menjelaskan sekarang adalah:
Tampaknya kita dapat membuat parser sederhana yang menemukan dan menulis ulang semua subselect dengan cara ini, dan menambahkannya ke pengait hibernasi untuk memanipulasi kueri asli.
sumber
SELECT
, seperti yang Anda miliki di pertanyaan pertama dalam pertanyaan?SELECT
terpisah, dan kemudian lakukan seleksi luar dengan daftar statis setelahIN
. Namun, itu secara signifikan lebih lambat (5-10 kali jika subquery memiliki lebih dari beberapa hasil), karena Anda memiliki round-trip jaringan tambahan plus Anda memiliki postgres memformat banyak hasil dan kemudian java mem-parsing hasil tersebut (dan kemudian melakukan sama lagi mundur). Solusi di atas melakukan hal yang sama secara semantik, sambil meninggalkan proses di dalam postgres. Secara keseluruhan, saat ini ini tampaknya menjadi cara tercepat dengan modifikasi terkecil dalam kasus kami.Jawab pertanyaan kedua: Ya, Anda bisa menambahkan ORDER BY ke subquery Anda, yang akan berdampak positif. Tapi itu mirip dengan solusi "EXISTS (subquery)" dalam kinerja. Ada perbedaan yang signifikan bahkan dengan subquery menghasilkan dua baris.
sumber