Bagaimana cara memaksa Postgres menggunakan indeks tertentu?

112

Bagaimana cara saya memaksa Postgres untuk menggunakan indeks yang sebaliknya akan bersikeras melakukan pemindaian berurutan?

mike
sumber
Digandakan, lihat stackoverflow.com/questions/14554302/…
Grigory Kislin
1
+1 Saya ingin sekali melihat fitur ini. Ini bukan masalah hanya menonaktifkan seq scan, seperti jawaban lain: kita membutuhkan kemampuan untuk memaksa PG menggunakan indeks tertentu . Ini karena dalam statistik kata sebenarnya bisa sepenuhnya salah dan pada titik itu Anda perlu menggunakan solusi yang tidak dapat diandalkan / parsial. Saya setuju bahwa dalam kasus sederhana Anda harus terlebih dahulu memeriksa indeks dan pengaturan lainnya, tetapi untuk keandalan dan penggunaan tingkat lanjut pada data besar, kami memerlukan ini.
collimarco
MySQL dan Oracle sama-sama memilikinya ... Tidak yakin mengapa perencana Postgres sangat tidak dapat diandalkan.
Kevin Parker

Jawaban:

103

Dengan asumsi Anda bertanya tentang fitur "petunjuk indeks" yang umum ditemukan di banyak database, PostgreSQL tidak menyediakan fitur seperti itu. Ini adalah keputusan sadar yang dibuat oleh tim PostgreSQL. Gambaran umum yang baik tentang mengapa dan apa yang dapat Anda lakukan dapat ditemukan di sini . Alasannya pada dasarnya karena ini adalah peretasan kinerja yang cenderung menyebabkan lebih banyak masalah di kemudian hari seiring dengan perubahan data Anda, sedangkan pengoptimal PostgreSQL dapat mengevaluasi ulang rencana berdasarkan statistik. Dengan kata lain, apa yang mungkin menjadi rencana kueri yang baik saat ini mungkin tidak akan menjadi rencana kueri yang baik untuk semua waktu, dan petunjuk indeks memaksa rencana kueri tertentu untuk sepanjang waktu.

Sebagai palu yang sangat tumpul, berguna untuk pengujian, Anda dapat menggunakan parameter enable_seqscandan enable_indexscan. Lihat:

Ini tidak cocok untuk penggunaan produksi yang berkelanjutan . Jika Anda memiliki masalah dengan pilihan paket kueri, Anda akan melihat dokumentasi untuk melacak masalah kinerja kueri . Jangan hanya mengatur enable_parameter dan pergi begitu saja.

Kecuali Anda memiliki alasan yang sangat bagus untuk menggunakan indeks, Postgres mungkin membuat pilihan yang tepat. Mengapa?

  • Untuk tabel kecil, lebih cepat melakukan pemindaian berurutan.
  • Postgres tidak menggunakan indeks ketika tipe data tidak cocok dengan benar, Anda mungkin perlu memasukkan cast yang sesuai.
  • Pengaturan perencana Anda mungkin menyebabkan masalah.

Lihat juga posting grup berita lama ini .

Patryk Kordylewski
sumber
4
Setuju, Memaksa postgres untuk melakukannya dengan cara Anda biasanya berarti Anda telah salah melakukannya. 9/10 Kali perencana akan mengalahkan apa pun yang dapat Anda hasilkan. 1 kali lainnya karena Anda salah.
Kent Fredric
Saya pikir itu adalah ide yang baik untuk memeriksa kelas operator yang benar-benar memegang indeks Anda.
metdos
2
Saya benci untuk menghidupkan kembali pertanyaan lama tetapi saya sering melihat di dokumentasi Postgres, diskusi dan di sini, tetapi apakah ada konsep umum untuk apa yang memenuhi syarat untuk meja kecil ? Apakah itu seperti 5000 baris, atau 50000 dll?
waffl
1
@waffl Sudahkah Anda mempertimbangkan untuk melakukan benchmarking? Buat tabel sederhana dengan indeks dan fungsi pendamping untuk mengisinya dengan n baris sampah acak. Kemudian mulailah melihat rencana kueri untuk nilai n yang berbeda . Saat Anda melihatnya mulai menggunakan indeks, Anda harus memiliki jawaban kasar. Anda juga bisa mendapatkan pemindaian berurutan jika PostgreSQL menentukan (berdasarkan statistik) bahwa pemindaian indeks juga tidak akan menghilangkan banyak baris. Jadi pembandingan selalu merupakan ide yang baik bila Anda memiliki masalah kinerja yang nyata. Sebagai tebakan lepas tangan dan anekdotal, menurut saya beberapa ribu biasanya "kecil".
jpmc26
11
Dengan pengalaman lebih dari 30 tahun di platform seperti Oracle, Teradata, dan MSSQL, saya menemukan pengoptimal PostgreSQL 10 tidak terlalu pintar. Bahkan dengan statistik terkini, ia menghasilkan rencana eksekusi yang kurang efisien daripada dipaksa ke arah khusus. Memberikan petunjuk struktural untuk mengkompensasi masalah ini akan memberikan solusi untuk memungkinkan PostgreSQL tumbuh di lebih banyak segmen pasar. MENURUT OPINI SAYA.
Guido Leenders
75

Mungkin satu-satunya alasan yang valid untuk menggunakan

set enable_seqscan=false

adalah saat Anda menulis kueri dan ingin segera melihat apa rencana kueri sebenarnya jika ada data dalam jumlah besar di tabel. Atau tentu saja jika Anda perlu segera mengonfirmasi bahwa kueri Anda tidak menggunakan indeks hanya karena kumpulan data terlalu kecil.

Niraj Bhawnani
sumber
41
jawaban singkat ini sebenarnya memberikan petunjuk yang baik untuk tujuan pengujian
dwery
3
Tidak ada yang menjawab pertanyaan itu!
Ivailo Bardarov
@IvailoBardarov Alasan mengapa semua saran lain ini ada di sini adalah karena PostgreSQL tidak memiliki fitur ini; ini adalah keputusan sadar yang dibuat oleh pengembang berdasarkan bagaimana biasanya digunakan dan masalah jangka panjang yang ditimbulkannya.
jpmc26
Trik yang bagus untuk diuji: jalankan set enable_seqscan=false, jalankan kueri Anda, lalu jalankan dengan cepat set enable_seqscan=trueuntuk mengembalikan postgresql ke perilaku yang semestinya (dan jelas jangan lakukan ini dalam produksi, hanya dalam pengembangan!)
Brian Hellekin
2
@BrianHellekin Lebih baik, SET SESSION enable_seqscan=falseuntuk hanya mempengaruhi diri sendiri
Izkata
20

Terkadang PostgreSQL gagal membuat pilihan indeks terbaik untuk kondisi tertentu. Sebagai contoh, misalkan ada tabel transaksi dengan beberapa juta baris, yang jumlahnya beberapa ratus untuk hari tertentu, dan tabel tersebut memiliki empat indeks: transaction_id, client_id, date, dan description. Anda ingin menjalankan kueri berikut:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQL dapat memilih untuk menggunakan indeks transaction_description_idx daripada transaction_date_idx, yang dapat menyebabkan kueri membutuhkan waktu beberapa menit alih-alih kurang dari satu detik. Jika demikian, Anda dapat memaksa menggunakan indeks pada tanggal dengan memalsukan kondisi seperti ini:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id
Ziggy Crueltyfree Zeitgeister
sumber
3
Ide bagus. Namun, ketika kami menonaktifkan penggunaan indeks saat ini dengan metode ini - pengoptimal kueri postgresql mundur ke indeks berikutnya yang sesuai. Dengan demikian, tidak ada jaminan bahwa pengoptimal akan memilih your_wanted_index, bisa jadi mesin postgresql hanya akan melakukan pemindaian urutan / kunci primer saja. Kesimpulan - tidak ada metode yang 100% dapat diandalkan untuk memaksa beberapa penggunaan indeks untuk server PostgreSql.
Agnius Vasiliauskas
Bagaimana jika tidak ada wherekondisi kecuali dua tabel atau bergabung dan Postgres gagal mengambil indeks.
Luna Lovegood
@Surya di atas berlaku untuk kondisi DI MANA dan untuk BERGABUNG ... ON
Ziggy Crueltyfree Zeitgeister
18

Jawaban singkat

Masalah ini biasanya terjadi ketika perkiraan biaya pemindaian indeks terlalu tinggi dan tidak mencerminkan kenyataan dengan benar. Anda mungkin perlu menurunkan random_page_costparameter konfigurasi untuk memperbaikinya. Dari dokumentasi Postgres :

Mengurangi nilai ini [...] akan menyebabkan sistem memilih pemindaian indeks; menaikkannya akan membuat pemindaian indeks terlihat relatif lebih mahal.

Anda dapat memeriksa apakah nilai yang lebih rendah benar-benar akan membuat Postgres menggunakan indeks (tetapi gunakan ini hanya untuk pengujian ):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

Anda dapat mengembalikan nilai default dengan SET random_page_cost = DEFAULT;lagi.

Latar Belakang

Pemindaian indeks memerlukan pengambilan halaman disk yang tidak berurutan. Postgres menggunakan random_page_costuntuk memperkirakan biaya pengambilan tidak berurutan dalam kaitannya dengan pengambilan berurutan. Nilai defaultnya adalah 4.0, dengan demikian mengasumsikan faktor biaya rata - rata 4 dibandingkan dengan pengambilan berurutan (dengan mempertimbangkan efek cache).

Namun masalahnya adalah bahwa nilai default ini tidak sesuai dalam skenario penting kehidupan nyata berikut ini:

1) Drive solid-state

Seperti yang diakui dalam dokumentasi:

Penyimpanan yang memiliki biaya pembacaan acak yang rendah relatif terhadap sekuensial, misalnya solid-state drive, mungkin lebih baik dimodelkan dengan nilai yang lebih rendah random_page_cost.

Menurut poin terakhir slide ini dari pidato di PostgresConf 2018, random_page_costharus diatur ke sesuatu antara 1.0dan 2.0untuk solid-state drive.

2) Data cache

Jika data indeks yang diperlukan sudah disimpan dalam cache dalam RAM, pemindaian indeks akan selalu jauh lebih cepat daripada pemindaian sekuensial. Dokumentasinya mengatakan:

Sejalan dengan itu, jika data Anda kemungkinan besar berada dalam cache, [...] penurunan random_page_costdapat dilakukan.

Masalahnya adalah Anda tentu tidak dapat dengan mudah mengetahui apakah data yang relevan sudah di-cache. Namun, jika indeks tertentu sering ditanyakan, dan jika sistem memiliki RAM yang memadai, maka data kemungkinan besar akan di-cache, dan random_page_costharus disetel ke nilai yang lebih rendah. Anda harus bereksperimen dengan nilai yang berbeda dan melihat mana yang berhasil untuk Anda.

Anda mungkin juga ingin menggunakan ekstensi pg_prewarm untuk cache data eksplisit.


emkey08
sumber
2
Saya bahkan harus mengatur random_page_cost = 0,1 untuk membuat pemindaian indeks berfungsi pada tabel baris besar (~ 600 juta baris) di Pg 10.1 di Ubuntu. Tanpa tweak, seq scan (meskipun paralel) memakan waktu 12 menit (Perhatikan bahwa tabel Analisis dilakukan!). Drive adalah SSD. Setelah tweak, waktu exec menjadi 1 detik.
Anatoly Alekseev
Anda menyelamatkan hari saya. Saya menjadi gila mencoba mencari tahu bagaimana kueri yang sama persis pada database yang sama membutuhkan waktu 30 detik di satu mesin dan kurang dari 1 di mesin lain, bahkan setelah menjalankan analisis di kedua ujungnya ... Kepada siapa yang berkepentingan: perintah ' ALTER SYSTEM SET random_page_cost = x 'menetapkan nilai default baru secara global.
Julien
10

Pertanyaan itu sendiri sangat tidak valid. Memaksa (dengan melakukan enable_seqscan = off misalnya) adalah ide yang sangat buruk. Mungkin berguna untuk memeriksa apakah itu akan lebih cepat, tetapi kode produksi tidak boleh menggunakan trik seperti itu.

Sebagai gantinya - jelaskan analisis kueri Anda, baca, dan cari tahu mengapa PostgreSQL memilih paket yang buruk (menurut pendapat Anda).

Ada alat di web yang membantu membaca menjelaskan hasil analisis - salah satunya adalah menjelaskan.depesz.com - yang ditulis oleh saya.

Pilihan lainnya adalah bergabung dengan saluran #postgresql di jaringan irc freenode , dan berbicara dengan orang-orang di sana untuk membantu Anda - karena mengoptimalkan kueri bukanlah masalah "ajukan pertanyaan, dapatkan jawaban dengan senang hati". Ini lebih seperti percakapan, dengan banyak hal untuk diperiksa, banyak hal untuk dipelajari.

pengguna80168
sumber
2

Ada trik untuk mendorong postgres agar lebih memilih seqscan yang menambahkan a OFFSET 0di subquery

Ini berguna untuk mengoptimalkan permintaan yang menautkan tabel besar / besar ketika yang Anda butuhkan hanyalah n elemen pertama / terakhir.

Katakanlah Anda mencari 20 elemen pertama / terakhir yang melibatkan banyak tabel yang memiliki 100k (atau lebih) entri, tidak ada gunanya membangun / menghubungkan semua kueri di semua data ketika apa yang akan Anda cari ada di 100 atau 1000 pertama entri. Dalam skenario ini misalnya, ternyata lebih dari 10x lebih cepat untuk melakukan pemindaian berurutan.

lihat Bagaimana cara mencegah Postgres agar tidak menyejajarkan subquery?

Antony Gibbs
sumber
Trik yang bagus. Meskipun pengoptimal yang baik tentu saja harus mengoptimalkan offset 0 :-)
Guido Leenders