Indeks yang tidak digunakan, belum memengaruhi kueri

8

Saya punya tabel PostgreSQL 9.3 dengan beberapa angka dan beberapa data tambahan:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Tabel ini saat ini memiliki sekitar 10 juta catatan dan membutuhkan 1GB ruang disk. myidtidak berturut-turut.

Saya ingin menghitung berapa baris di setiap blok yang berisi 100000 angka berurutan:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Ini mengembalikan sekitar 3500 baris.

Saya perhatikan bahwa keberadaan indeks tertentu secara signifikan mempercepat kueri ini meskipun rencana kueri tidak menyebutkannya sama sekali. Paket permintaan tanpa indeks:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

Indeks:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

Paket kueri baru:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Jadi, rencana kueri dan runtime berbeda secara signifikan (hampir tiga kali) tetapi tidak disebutkan indeksnya. Perilaku ini dapat direproduksi dengan sempurna di mesin dev saya: Saya mengalami beberapa siklus menjatuhkan indeks, menguji kueri beberapa kali, membuat ulang indeks, dan lagi menguji kueri beberapa kali. Apa yang sedang terjadi disini?

liori
sumber
Saya bukan ahli dalam menganalisis rencana kueri Postgres, tetapi saya rasa indeks digunakan untuk HashAggregatemetode ini (dan tidak diperlukan penyortiran), sehingga Anda mendapatkan kinerja yang lebih baik. Mengapa indeks tidak disebutkan dalam rencana, saya belum tahu.
ypercubeᵀᴹ
Apakah output dari perubahan rencana jika Anda mengaktifkan mode verbose menggunakan: explain (analyze true, verbose true) ...?
a_horse_with_no_name
Akan lebih bagus jika Anda bisa merebus yang satu ini menjadi test case yang lengkap. Tampaknya aneh.
Craig Ringer
@ a_horse_with_no_name: Ya, itu berubah — saya telah mengganti paket kueri dengan yang verbose dalam pertanyaan. Tetapi rencana permintaan itu masih tidak menyebutkan indeks sama sekali.
liori
Jika ada lebih banyak statistik yang tersedia (khususnya kardinalitas dan kemungkinan nilai min / maks) pada kolom id dengan indeks daripada tanpa, itu dapat mengubah grup optimizer dengan pemilihan metode, bahkan jika akhirnya tidak menggunakan indeks sama sekali . (Saya tidak tahu pengoptimal & statistik postgres sama sekali, jadi tidak tahu apakah itu bisa terjadi atau tidak.)
Mat

Jawaban:

3

VACUUM ANALYZEmembuat perbedaan dalam contoh Anda. Plus, seperti yang disediakan @jjanes , statistik tambahan untuk indeks fungsional. Per dokumentasi:

pg_statisticjuga menyimpan data statistik tentang nilai ekspresi indeks. Ini digambarkan seolah-olah mereka adalah kolom data aktual; khususnya, starelidreferensi indeks. Namun, tidak ada entri yang dibuat untuk kolom indeks non-ekspresi biasa, karena akan berlebihan dengan entri untuk kolom tabel yang mendasarinya.

Namun, membuat indeks tidak dengan sendirinya menyebabkan Postgres mengumpulkan statistik. Mencoba:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Tidak mengembalikan apa pun hingga Anda menjalankan yang pertama ANALYZE(atau VACUUM ANALYZE, daemon autovacuum masuk).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Sekarang Anda akan melihat statistik tambahan.

Karena seluruh tabel harus tetap dibaca, Postgres akan menggunakan pemindaian berurutan kecuali ia mengharapkan perhitungan myid/100000menjadi cukup mahal untuk beralih, padahal sebenarnya tidak.

Satu-satunya kesempatan Anda lainnya adalah pemindaian hanya indeks jika indeks jauh lebih kecil dari tabel - dan prasyarat untuk pemindaian hanya indeks terpenuhi. Detail dalam Postgres Wiki dan manual .

Selama indeks fungsional tidak digunakan, manfaat jaminan dari statistik tambahan adalah moderat. Jika tabel ini hanya dapat dibaca, biayanya akan rendah - tetapi sekali lagi, kami mungkin akan segera memindai indeks saja.

Mungkin Anda juga dapat mencapai rencana kueri yang lebih baik dengan menetapkan target statistik yang lebih tinggi mytable.myid. Itu hanya akan menimbulkan biaya kecil. Lebih:

Erwin Brandstetter
sumber
Terima kasih atas penjelasan ini, sangat membantu dalam memahami masalah. Dalam kasus saya, saya kemungkinan besar akan membutuhkan myid/100000 BETWEEN somevalue AND othervaluekondisi tambahan , jadi indeks akan digunakan dalam rencana kueri — saya baru saja menanyakan pertanyaan ini karena saya tidak mengerti mengapa indeks berguna dalam seluruh tabel.
liori
@liori: Anda bisa menutupi bahwa dengan WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(mempertimbangkan pembulatan efek tergantung pada jenis Anda), dan Anda mungkin sudah memiliki indeks polos pada myid, sehingga Anda dapat melakukannya tanpa indeks khusus tambahan. Mungkin lebih efisien.
Erwin Brandstetter
6

Ketika Anda membuat indeks ekspresi, itu menyebabkan PostgreSQL untuk mengumpulkan statistik pada ekspresi itu. Dengan statistik tersebut, sekarang memiliki perkiraan akurat untuk jumlah baris teragregasi yang akan dikembalikan oleh kueri, yang mengarahkannya untuk membuat pilihan rencana yang lebih baik.

Khususnya dalam kasus ini, tanpa statistik tambahan itu menurutnya tabel hash akan terlalu besar untuk muat di work_mem, jadi itu tidak memilih metode itu.

jjanes
sumber
Saya pikir perencana tidak memperhitungkan nilai work_mem. Jika Anda menaikkannya sehingga jenisnya cocok dengan memori, apakah masih akan menggunakan paket yang sama. Biarkan saya perhatikan di sini bahwa perbedaan waktu (sebagian besar) berasal dari jenis disk eksternal.
dezso
1
@dezso Bagaimana jika Anda melakukan percobaan dua atau tiga kali lipat nilai work_mem yang diperlukan agar sesuai dengan jenis memori? Penyortiran dan hashing memiliki estimasi overhead yang berbeda, dan estimasi itu sendiri tidak terlalu tepat. Juga, versi minor 9,3 apa yang Anda gunakan?
jjanes