Apakah klausa WHERE diterapkan dalam urutan yang ditulis?

36

Saya mencoba mengoptimalkan kueri yang terlihat dalam tabel besar (37 juta baris) dan memiliki pertanyaan tentang urutan operasi yang dijalankan dalam kueri.

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

Apakah WHEREklausa untuk rentang tanggal dijalankan sebelum subquery? Apakah ini cara yang baik untuk menempatkan klausa yang paling ketat terlebih dahulu untuk menghindari loop besar untuk klausa lain, untuk melakukan eksekusi yang lebih cepat?

Sekarang pertanyaan membutuhkan banyak waktu untuk dieksekusi.

Jorge Vega Sánchez
sumber

Jawaban:

68

Untuk menguraikan jawaban @ alci:

PostgreSQL tidak peduli urutan apa yang Anda tulis

  • PostgreSQL sama sekali tidak peduli tentang urutan entri dalam WHEREklausa, dan memilih indeks dan urutan eksekusi berdasarkan estimasi biaya dan selektivitas saja.

  • Urutan penggabungan ditulis juga diabaikan hingga dikonfigurasi join_collapse_limit; jika ada lebih banyak gabungan dari itu, itu akan mengeksekusinya dalam urutan yang ditulis.

  • Subquery dapat dieksekusi sebelum atau setelah query yang berisi mereka, tergantung pada apa yang tercepat, selama subquery dieksekusi sebelum query luar benar-benar membutuhkan informasi. Seringkali dalam kenyataannya subquery dieksekusi semacam-di tengah, atau disisipkan dengan permintaan luar.

  • Tidak ada jaminan PostgreSQL akan benar-benar mengeksekusi bagian dari permintaan sama sekali. Mereka dapat sepenuhnya dioptimalkan. Ini penting jika Anda memanggil fungsi dengan efek samping.

PostgreSQL akan mengubah kueri Anda

PostgreSQL akan sangat mengubah pertanyaan sambil mempertahankan efek yang sama persis, untuk membuatnya berjalan lebih cepat tanpa mengubah hasil.

  • Istilah di luar subquery dapat didorong ke dalam subquery sehingga dieksekusi sebagai bagian dari subquery bukan tempat Anda menulisnya di kueri luar

  • Istilah dalam subquery dapat ditarik ke luar permintaan sehingga eksekusi mereka dilakukan sebagai bagian dari permintaan luar, bukan di mana Anda menulisnya dalam subquery

  • Subquery dapat, dan seringkali, diratakan menjadi gabungan di meja luar. Hal yang sama berlaku untuk hal-hal seperti EXISTSdan NOT EXISTSpertanyaan.

  • Tampilan diratakan ke dalam kueri yang menggunakan tampilan

  • Fungsi SQL sering dimasukkan ke dalam permintaan panggilan

  • ... dan ada banyak transformasi lain yang dibuat untuk kueri, seperti pra-evaluasi ekspresi konstan, de-korelasi beberapa subkueri, dan segala macam trik perencana / pengoptimal lainnya.

Secara umum PostgreSQL dapat secara besar-besaran mengubah dan menulis ulang kueri Anda, ke titik di mana masing-masing kueri ini:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

biasanya semua akan menghasilkan rencana permintaan yang persis sama. (Dengan asumsi saya tidak melakukan kesalahan bodoh di atas pula).

Tidak jarang mencoba mengoptimalkan kueri hanya untuk mengetahui bahwa perencana kueri telah menemukan trik yang Anda coba dan terapkan secara otomatis, sehingga versi yang dioptimalkan dengan tangan tidak lebih baik daripada yang asli.

Keterbatasan

Perencana / pengoptimal jauh dari omnicient, dan dibatasi oleh persyaratan untuk benar-benar yakin itu tidak dapat mengubah efek kueri, data yang tersedia untuk membuat keputusan, aturan yang telah diterapkan, dan waktu CPU itu bisa menghabiskan merenungkan optimasi. Sebagai contoh:

  • Perencana bergantung pada statistik yang disimpan oleh ANALYZE(biasanya melalui autovacuum). Jika ini sudah usang, pilihan paket bisa jadi buruk.

  • Statistik hanya sampel, sehingga dapat menyesatkan karena efek pengambilan sampel, terutama jika sampel terlalu kecil diambil. Pilihan rencana yang buruk dapat terjadi.

  • Statistik tidak melacak beberapa jenis data tentang tabel, seperti korelasi antar kolom. Ini dapat membuat perencana untuk membuat keputusan buruk ketika mengasumsikan hal-hal independen ketika mereka tidak.

  • Perencana bergantung pada parameter biaya ingin random_page_costmengatakan kecepatan relatif dari berbagai operasi pada sistem tertentu yang diinstal. Ini hanya panduan. Jika mereka salah besar, mereka dapat menyebabkan pilihan rencana yang buruk.

  • Setiap subquery dengan LIMITatau OFFSETtidak bisa diratakan atau mengalami pullup / pushdown. Ini tidak berarti itu akan dijalankan sebelum semua bagian dari permintaan luar, atau bahkan itu akan dieksekusi sama sekali .

  • Istilah CTE (klausa dalam WITHkueri) selalu dieksekusi secara keseluruhan, jika dieksekusi sama sekali. Mereka tidak dapat diratakan, dan persyaratan tidak dapat didorong ke atas atau ditarik ke bawah penghalang istilah CTE. Istilah CTE selalu dijalankan sebelum permintaan terakhir. Ini adalah perilaku non-SQL-standar , tetapi ini didokumentasikan sebagai bagaimana PostgreSQL melakukan sesuatu.

  • PostgreSQL memiliki kemampuan terbatas untuk mengoptimalkan lintas kueri pada tabel asing, security_barriertampilan, dan jenis hubungan khusus lainnya

  • PostgreSQL tidak akan menyejajarkan fungsi yang ditulis dalam apa pun kecuali SQL biasa, juga tidak melakukan pullup / pushdown di antaranya dan permintaan luar.

  • Perencana / pengoptimal sangat bodoh dalam memilih indeks ekspresi, dan tentang perbedaan tipe data sepele antara indeks dan ekspresi.

Masih banyak lagi.

Permintaan anda

Dalam hal permintaan Anda:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

tidak ada yang menghentikannya untuk diratakan menjadi kueri yang lebih sederhana dengan set gabungan tambahan, dan itu sangat mungkin terjadi.

Mungkin akan menghasilkan sesuatu seperti (tidak teruji, jelas):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

PostgreSQL kemudian akan mengoptimalkan urutan bergabung dan bergabung metode berdasarkan selektivitas dan perkiraan jumlah baris dan indeks yang tersedia. Jika ini mencerminkan kenyataan secara wajar maka ia akan melakukan penggabungan dan menjalankan entri klausa mana dalam urutan apa pun yang terbaik - seringkali mencampurkannya bersama-sama, sehingga melakukan sedikit hal ini, kemudian sedikit itu, kemudian kembali ke bagian pertama , dll.

Cara melihat apa yang dilakukan pengoptimal

Anda tidak dapat melihat SQL tempat PostgreSQL mengoptimalkan kueri Anda, karena SQL mengonversi SQL ke representasi kueri internal kemudian memodifikasinya. Anda dapat membuang paket kueri dan membandingkannya dengan kueri lain.

Tidak ada cara untuk "menyebarkan" rencana kueri atau struktur rencana internal kembali ke SQL.

http://explain.depesz.com/ memiliki penolong paket permintaan yang layak. Jika Anda benar-benar baru dalam rencana kueri dll (dalam hal ini saya kagum Anda berhasil sejauh ini melalui posting ini) maka PgAdmin memiliki penampil rencana kueri grafis yang memberikan informasi lebih sedikit tetapi lebih sederhana.

Bacaan terkait:

Kemampuan pushdown / pullup dan perataan terus meningkat di setiap rilis . PostgreSQL biasanya benar tentang keputusan pull-up / push-down / flattening, tetapi tidak selalu, jadi kadang-kadang Anda harus (ab) menggunakan CTE atau OFFSET 0hack. Jika Anda menemukan kasus seperti itu, laporkan bug perencana permintaan.


Jika Anda benar-benar tertarik, Anda juga dapat menggunakan debug_print_plansopsi untuk melihat paket kueri mentah, tetapi saya berjanji Anda tidak ingin membacanya. Sangat.

Craig Ringer
sumber
Wow ... jawaban yang cukup lengkap :-) Salah satu kasus di mana saya punya rencana lambat dengan Postgresql (dan juga dengan mesin DB terkenal lainnya, seperti Oracle), adalah dengan korelasi antara kolom atau beberapa gabungan yang berkorelasi. Seringkali akan berakhir dengan loop bersarang, berpikir hanya ada beberapa baris pada titik ini dari rencana, padahal sebenarnya ada ribuan. Salah satu cara untuk mengoptimalkan kueri semacam ini adalah dengan 'set enable_nestloop = off;' selama durasi permintaan.
alci
Saya mengalami situasi di mana v9.5.5 mencoba menerapkan TO_DATE sebelum memeriksa apakah itu dapat diterapkan, dalam 7 sederhana di mana klausa kueri. Pesanan penting.
user1133275
@ user1133275 Dalam hal ini, itu hanya bekerja untuk Anda secara kebetulan, karena perkiraan biaya kalkulasi sama. PostgreSQL mungkin masih memutuskan untuk berjalan to_datesebelum memeriksa versi yang lebih baru atau karena beberapa perubahan statistik pengoptimal. Untuk menjalankan pemeriksaan secara andal sebelum fungsi yang seharusnya hanya berjalan setelah pemeriksaan, gunakan CASEpernyataan.
Craig Ringer
salah satu jawaban terhebat yang pernah saya lihat di SO! Jempol, kawan!
62mkv
Saya memang mengalami situasi di mana untuk menambahkan permintaan sederhana order bymembuat eksekusi permintaan jauh lebih cepat daripada jika tidak ada order by. Itulah salah satu alasan saya menulis pertanyaan saya dengan bergabung sedemikian rupa seolah-olah saya ingin mereka dieksekusi - itu bagus untuk memiliki pengoptimal yang hebat tapi saya pikir itu tidak bijaksana untuk mempercayai nasib Anda sepenuhnya untuk hasil itu dan menulis pertanyaan tanpa memikirkan bagaimana itu may bedieksekusi ... Great jawaban !!
Greg0ry
17

SQL adalah bahasa deklaratif: Anda memberi tahu apa yang Anda inginkan, bukan bagaimana melakukannya. RDBMS akan memilih cara menjalankan kueri, yang disebut rencana eksekusi.

Sekali waktu (5-10 tahun yang lalu), cara kueri ditulis memiliki dampak langsung pada rencana eksekusi, tetapi saat ini, sebagian besar mesin basis data SQL menggunakan Cost Based Optimizer untuk perencanaan. Artinya, ia akan mengevaluasi berbagai strategi untuk mengeksekusi kueri, berdasarkan statistiknya pada objek basis data, dan memilih yang terbaik.

Sebagian besar waktu, itu benar-benar yang terbaik, tetapi kadang-kadang mesin DB akan membuat pilihan yang buruk, menghasilkan pertanyaan yang sangat lambat.

alci
sumber
Perlu dicatat bahwa pada beberapa permintaan RDBMS urutan masih signifikan, tetapi untuk yang lebih maju semua yang Anda katakan adalah benar dalam praktik maupun teori. Ketika perencana kueri memilih pilihan yang salah dari perintah eksekusi, biasanya ada petunjuk kueri yang tersedia untuk mendorongnya ke arah yang lebih efisien (seperti WITH(INDEX(<index>))di MSSQL untuk memaksa pilihan indeks untuk gabungan tertentu).
David Spillett
Pertanyaannya adalah apakah beberapa indeks pada date_daybenar-benar ada. Jika tidak ada maka optimizer tidak memiliki banyak rencana untuk dibandingkan.
jkavalik