Memahami biaya Postgres EXPLAIN
EXPLAIN
sangat berguna untuk memahami kinerja kueri Postgres. Ini mengembalikan rencana eksekusi yang dihasilkan oleh perencana kueri PostgreSQL untuk pernyataan yang diberikan. EXPLAIN
perintah menentukan apakah tabel yang dirujuk dalam pernyataan akan dicari menggunakan pemindaian indeks atau pemindaian berurutan.
Beberapa hal pertama yang akan Anda perhatikan saat meninjau keluaran EXPLAIN
perintah adalah statistik biaya, jadi wajar untuk bertanya-tanya apa artinya, bagaimana mereka dihitung, dan bagaimana mereka digunakan.
Singkatnya, perencana kueri PostgreSQL memperkirakan berapa banyak waktu yang dibutuhkan kueri (dalam unit arbitrer), dengan biaya awal dan total biaya untuk setiap operasi. Lebih lanjut tentang itu nanti. Ketika memiliki beberapa opsi untuk mengeksekusi kueri, ia menggunakan biaya ini untuk memilih opsi termurah, dan oleh karena itu diharapkan tercepat.
Berapa unit biayanya?
Biaya dalam satuan arbitrer. Kesalahpahaman yang umum adalah bahwa mereka berada dalam milidetik atau beberapa unit waktu lainnya, tetapi bukan itu masalahnya.
Unit biaya ditambatkan (secara default) ke satu halaman berurutan yang dibaca seharga 1,0 unit (seq_page_cost
). Setiap baris yang diproses menambahkan 0,01 (cpu_tuple_cost
), dan setiap pembacaan halaman yang tidak berurutan menambahkan 4.0 (random_page_cost
). Ada lebih banyak konstanta seperti ini, yang semuanya dapat dikonfigurasi. Yang terakhir adalah kandidat yang sangat umum, setidaknya pada perangkat keras modern. Kami akan membahasnya sebentar lagi.
Biaya Awal
Angka pertama yang Anda lihat setelah cost=
dikenal sebagai "biaya awal". Ini adalah perkiraan berapa lama waktu yang dibutuhkan untuk mengambil baris pertama . Dengan demikian, biaya awal suatu operasi termasuk biaya anak-anaknya.
Untuk pemindaian berurutan, biaya startup umumnya mendekati nol, karena dapat langsung mulai mengambil baris. Untuk operasi pengurutan, akan lebih tinggi karena sebagian besar pekerjaan harus dilakukan sebelum baris dapat mulai dikembalikan.
Untuk melihat contoh, mari buat tabel pengujian sederhana dengan 1000 nama pengguna:
BUAT pengguna TABEL ( id bigint DIHASILKAN SELALU SEBAGAI KUNCI UTAMA IDENTITAS, teks nama pengguna NOT NULL);MASUKKAN KE pengguna (namapengguna)PILIH 'orang' || nFROM generate_series(1, 1000) SEBAGAI n;ANALISIS pengguna;
Mari kita lihat rencana kueri sederhana, dengan beberapa operasi:
JELASKAN PILIH * DARI pengguna ORDER BY username;QUERY PLAN |----------------------------------- ---------------------------+Urutkan (biaya=66.83..69.33 baris=1000 lebar=17) | Sortir Kunci:nama pengguna | -> Pemindaian Seq pada pengguna (biaya=0.00..17.00 baris=1000 lebar=17)|
Dalam rencana kueri di atas, seperti yang diharapkan, perkiraan biaya eksekusi pernyataan untuk Seq Scan
adalah 0.00
, dan untuk Sort
adalah 66.83
.
Total biaya
Statistik biaya kedua, setelah biaya awal dan dua titik, dikenal sebagai "biaya total". Ini adalah perkiraan berapa lama waktu yang dibutuhkan untuk mengembalikan semua baris .
Mari kita lihat contoh rencana kueri itu lagi:
RENCANA PERMINTAAN |------------------------------------------------------- ------------------+Urutkan (biaya=66.83..69.33 baris=1000 lebar=17) | Sortir Kunci:nama pengguna | -> Pemindaian Seq pada pengguna (biaya=0.00..17.00 baris=1000 lebar=17)|
Kita dapat melihat bahwa total biaya Seq Scan
operasi 17.00
. Untuk Sort
operasi adalah 69,33, yang tidak lebih dari biaya startup (seperti yang diharapkan).
Biaya total biasanya mencakup biaya operasi yang mendahuluinya. Misalnya, total biaya operasi Sortir di atas termasuk Pemindaian Seq.
Pengecualian penting adalah LIMIT
klausa, yang digunakan perencana untuk memperkirakan apakah dapat menggugurkan lebih awal. Jika hanya membutuhkan sejumlah kecil baris, kondisi yang umum, mungkin menghitung bahwa pilihan pemindaian yang lebih sederhana lebih murah (mungkin lebih cepat).
Misalnya:
MENJELASKAN PILIH * DARI pengguna LIMIT 1;QUERY PLAN |------------------------------------ --------------------------+Batas (biaya=0.00..0.02 baris=1 lebar=17) | -> Pemindaian Seq pada pengguna (biaya=0.00..17.00 baris=1000 lebar=17)|
Seperti yang Anda lihat, total biaya yang dilaporkan pada node Seq Scan masih 17,00, tetapi biaya penuh dari operasi Batas dilaporkan 0,02. Ini karena perencana berharap hanya perlu memproses 1 baris dari 1000, sehingga biaya, dalam hal ini, diperkirakan 1000 dari total.
Bagaimana biaya dihitung
Untuk menghitung biaya ini, perencana kueri Postgres menggunakan konstanta (beberapa di antaranya telah kita lihat) dan metadata tentang konten database. Metadata sering disebut sebagai “statistik”.
Statistik dikumpulkan melalui ANALYZE
(jangan bingung dengan EXPLAIN
parameter dengan nama yang sama), dan disimpan di pg_statistic
. Mereka juga disegarkan secara otomatis sebagai bagian dari autovacuum.
Statistik ini mencakup sejumlah hal yang sangat berguna, seperti kira-kira jumlah baris yang dimiliki setiap tabel, dan nilai paling umum di setiap kolom.
Mari kita lihat contoh sederhana, menggunakan data kueri yang sama seperti sebelumnya:
Jelaskan jumlah PILIH(*) DARI pengguna;PLAN QUERY |----------------------------------- --------------------------+Agregat (biaya=19.50..19.51 baris=1 lebar=8) | -> Pemindaian Seq pada pengguna (biaya=0.00..17.00 baris=1000 lebar=0)|
Dalam kasus kami, statistik perencana menyarankan data untuk tabel disimpan dalam 7 halaman (atau blok), dan 1000 baris akan dikembalikan. Parameter biaya seq_page_cost
, cpu_tuple_cost
, dan cpu_operator_cost
dibiarkan pada default 1
, 0.01
, dan 0.0025
masing-masing.
Dengan demikian, total biaya Seq Scan dihitung sebagai:
Total biaya Seq Scan=(perkiraan pembacaan halaman berurutan * seq_page_cost) + (perkiraan baris yang dikembalikan * cpu_tuple_cost)=(7 * 1) + (1000 * 0,01)=7 + 10.00=17.00
Dan untuk Agregat sebagai:
Total biaya Agregat=(biaya Seq Scan) + (perkiraan baris yang diproses * cpu_operator_cost) + (perkiraan baris yang dikembalikan * cpu_tuple_cost)=(17,00) + (1000 * 0,0025) + (1 * 0,01) =17,00 + 2,50 + 0,01=19,51
Bagaimana perencana menggunakan biaya
Karena kami tahu Postgres akan memilih paket kueri dengan total biaya terendah, kami dapat menggunakannya untuk mencoba memahami pilihan yang telah dibuat. Misalnya, jika kueri tidak menggunakan indeks yang Anda harapkan, Anda dapat menggunakan setelan seperti enable_seqscan
untuk secara besar-besaran mencegah pilihan rencana kueri tertentu. Pada titik ini, Anda tidak perlu terkejut mendengar bahwa setelan seperti ini berfungsi dengan meningkatkan biaya!
Nomor baris merupakan bagian yang sangat penting dalam estimasi biaya. Mereka digunakan untuk menghitung perkiraan untuk pesanan bergabung yang berbeda, algoritma bergabung, jenis pemindaian, dan banyak lagi. Perkiraan biaya baris yang keluar banyak dapat menyebabkan estimasi biaya keluar banyak, yang pada akhirnya dapat menghasilkan pilihan rencana yang kurang optimal.
Menggunakan EXPLAIN ANALYZE untuk mendapatkan rencana kueri
Saat Anda menulis pernyataan SQL di PostgreSQL, ANALYZE
perintah adalah kunci untuk mengoptimalkan kueri, membuatnya lebih cepat dan lebih efisien. Selain menampilkan rencana kueri dan perkiraan PostgreSQL, EXPLAIN ANALYZE
opsi melakukan kueri (hati-hati dengan UPDATE
dan DELETE
!), dan menunjukkan waktu eksekusi aktual dan jumlah baris untuk setiap langkah dalam proses eksekusi. Ini diperlukan untuk memantau kinerja SQL.
Anda dapat menggunakan EXPLAIN ANALYZE
untuk membandingkan perkiraan jumlah baris dengan baris sebenarnya yang dikembalikan oleh setiap operasi.
Mari kita lihat contoh, menggunakan data yang sama lagi:
RENCANA PERMINTAAN |------------------------------------------------------- -------------------------------------------------- -------------+Urutkan (biaya=66.83..69.33 baris=1000 lebar=17) (waktu sebenarnya=20.569..20.684 baris=1000 loop=1) | Sortir Kunci:nama pengguna | Metode Sortir:quicksort Memori:102kB | -> Seq Memindai pengguna (biaya=0.00..17.00 baris=1000 lebar=17) (waktu aktual=0.048..0.596 baris=1000 loop=1)|Waktu Perencanaan:0,171 md |Waktu Eksekusi:20,793 md |Kita bisa melihat bahwa total biaya eksekusi masih 69,33, dengan mayoritas operasi Sort, dan 17,00 berasal dari Sequential Scan. Perhatikan bahwa waktu eksekusi kueri hanya di bawah 21 md.
Pemindaian berurutan vs. Pemindaian Indeks
Sekarang, mari tambahkan indeks untuk mencoba menghindari jenis tabel yang mahal itu:
BUAT INDEKS people_username_idx PADA pengguna (nama pengguna);JELASKAN ANALISIS PILIH * DARI pengguna ORDER BY username;QUERY PLAN |----------------------- -------------------------------------------------- -------------------------------------------------- ------+Pemindaian Indeks menggunakan people_username_idx pada pengguna (biaya=0.28..28.27 baris=1000 lebar=17) (waktu aktual=0,052..1.494 baris=1000 loop=1)|Waktu Perencanaan:0,186 md |Eksekusi Waktu:1,686 md |Seperti yang Anda lihat, perencana kueri kini telah memilih Pemindaian Indeks, karena total biaya paket tersebut adalah 28,27 (lebih rendah dari 69,33). Tampaknya pemindaian indeks lebih efisien daripada pemindaian berurutan, karena waktu eksekusi kueri sekarang hanya di bawah 2 md.
Membantu perencana memperkirakan lebih akurat
Kami dapat membantu perencana memperkirakan lebih akurat dengan dua cara:
- Bantu mereka mengumpulkan statistik yang lebih baik
- Setel konstanta yang digunakan untuk perhitungan
Statistik bisa sangat buruk setelah perubahan besar pada data dalam tabel. Dengan demikian, saat memuat banyak data ke dalam tabel, Anda dapat membantu Postgres dengan menjalankan
ANALYZE
manual di atasnya. Statistik juga tidak bertahan selama peningkatan versi utama, jadi itu adalah waktu penting lainnya untuk melakukan ini.Secara alami, tabel juga berubah seiring waktu, jadi menyetel setelan vakum otomatis untuk memastikannya berjalan cukup sering untuk beban kerja Anda bisa sangat membantu.
Jika Anda mengalami masalah dengan perkiraan buruk untuk kolom dengan distribusi miring, Anda dapat mengambil manfaat dari meningkatkan jumlah informasi yang dikumpulkan Postgres dengan menggunakan
ALTER TABLE SET STATISTICS
perintah, atau bahkandefault_statistics_target
untuk seluruh basis data.Penyebab umum perkiraan buruk lainnya adalah, secara default, Postgres akan menganggap bahwa dua kolom independen. Anda dapat memperbaikinya dengan memintanya mengumpulkan data korelasi pada dua kolom dari tabel yang sama melalui statistik yang diperluas.
Di bagian depan penyetelan konstan, ada banyak parameter yang dapat Anda setel agar sesuai dengan perangkat keras Anda. Dengan asumsi Anda menggunakan SSD, Anda mungkin paling tidak ingin menyetel setelan
random_page_cost
. Ini default ke 4, yang 4x lebih mahal daripadaseq_page_cost
kami melihat sebelumnya. Rasio ini masuk akal pada disk yang berputar, tetapi pada SSD, rasio ini cenderung terlalu banyak menghukum I/O acak. Karena pengaturan seperti itu lebih dekat ke 1, atau antara 1 dan 2, mungkin lebih masuk akal. Di ScaleGrid, defaultnya adalah 1.Dapatkah saya menghapus biaya dari paket kueri?
Untuk banyak alasan yang disebutkan di atas, kebanyakan orang membiarkan biaya saat menjalankan
EXPLAIN
. Namun, jika diinginkan, Anda dapat menonaktifkannya menggunakanCOSTS
parameter.Jelaskan (MATI BIAYA) PILIH * DARI pengguna LIMIT 1;QUERY PLAN |-----------------------+Limit | -> Pemindaian Seq pada pengguna|Kesimpulan
Untuk membatasi kembali, biaya dalam rencana kueri adalah perkiraan Postgres untuk berapa lama kueri SQL akan memakan waktu, dalam unit arbitrer.
Ini memilih paket dengan biaya keseluruhan terendah, berdasarkan beberapa konstanta yang dapat dikonfigurasi dan beberapa statistik yang telah dikumpulkannya.
Membantunya memperkirakan biaya ini dengan lebih akurat sangat penting untuk membantunya membuat pilihan yang baik, dan menjaga performa kueri Anda.
|