TL;DR
EXPLAIN di SQL nunjukin rencana eksekusi yang bakal dipakai database buat jalanin query kamu, tanpa beneran ngejalaninnya. EXPLAIN ANALYZE ngejalanin query-nya dan ngasih waktu asli tiap langkah, jadi kamu bisa lihat persis di mana waktunya kebuang. Cara pakainya cukup tempel kata EXPLAIN ANALYZE di depan query kamu, lalu baca hasilnya dari baris paling dalam ke luar.
EXPLAIN di SQL nunjukin rencana eksekusi yang bakal dipakai database buat jalanin query kamu, tanpa beneran ngejalaninnya.
EXPLAIN ANALYZE ngejalanin query-nya, lalu ngasih waktu asli tiap langkah. Ini yang kamu butuh kalau query lambat dan kamu gak tau kenapa.
Nebak-nebak penyebab query lambat itu buang waktu. Query plan ngasih jawabannya dalam 2 detik.
Waktu kamu nulis query, kamu bilang apa yang kamu mau. Database yang mutusin gimana ngambilnya, tabel mana dibaca duluan, index dipakai atau gak, JOIN-nya pakai metode apa.
Keputusan itu namanya query plan. Dan EXPLAIN adalah cara kamu ngintip keputusan itu.
Cara pakainya cukup tempel di depan query:
EXPLAIN
SELECT kota, SUM(total) AS omzet
FROM transaksi
WHERE tanggal >= '2026-01-01'
GROUP BY kota;
Query-nya gak dijalanin. Kamu cuma dikasih rencananya.
| EXPLAIN | EXPLAIN ANALYZE | |
|---|---|---|
| Query dijalanin? | Gak | Iya |
| Waktu yang ditampilkan | Estimasi (cost) | Waktu asli (actual time) |
| Jumlah baris | Tebakan | Tebakan + jumlah asli |
| Aman buat UPDATE/DELETE? | Aman | Bahaya: beneran jalan |
Baris terakhir itu penting. EXPLAIN ANALYZE DELETE FROM transaksi bakal beneran ngehapus datanya. Kalau kamu harus ngetes perintah yang ngubah data, bungkus di transaction:
BEGIN;
EXPLAIN ANALYZE UPDATE transaksi SET status = 'lunas' WHERE id < 100;
ROLLBACK;
Ini hasil EXPLAIN ANALYZE dari tabel transaksi berisi 2,4 juta baris:
HashAggregate (cost=48250.11..48250.16 rows=5 width=40)
(actual time=8142.331..8142.336 rows=5 loops=1)
Group Key: kota
-> Seq Scan on transaksi (cost=0.00..44120.00 rows=826022 width=12)
(actual time=0.019..7011.442 rows=831405 loops=1)
Filter: (tanggal >= '2026-01-01'::date)
Rows Removed by Filter: 1568595
Planning Time: 0.184 ms
Execution Time: 8142.402 ms
Cara bacanya: dari dalam ke luar. Baris yang paling menjorok ke kanan itu yang jalan duluan.
Jadi urutannya:
Seq Scan on transaksi: database baca semua 2,4 juta baris satu per satu.Filter: tanggal >= 2026-01-01. Dari situ dia buang 1.568.595 baris yang gak cocok.HashAggregate: sisanya digrupin per kota dan dijumlahin.Angka yang paling penting: Execution Time: 8142 ms. Delapan detik.
Dan actual time=0.019..7011.442 di baris Seq Scan nunjukin 7 dari 8 detik itu habis cuma buat baca tabel.
cost=0.00..44120.00: estimasi biaya. Angka pertama = biaya sampai baris pertama keluar. Angka kedua = biaya sampai semua baris keluar. Satuannya internal, bukan detik.rows=826022: tebakan database soal berapa baris yang bakal lolos.actual time=0.019..7011.442: waktu asli dalam milidetik.rows=831405 di bagian actual: jumlah baris asli yang keluar.loops=1: berapa kali langkah ini diulang.Bandingin rows estimasi (826.022) sama rows aktual (831.405). Beda tipis, berarti statistik tabelnya sehat.
Kalau bedanya jauh, misal estimasi 100 tapi aktual 500.000, itu alarm. Statistik kamu basi dan database salah nebak. Perbaiki dengan:
ANALYZE transaksi;
| Node | Artinya | Bagus atau jelek? |
|---|---|---|
Seq Scan | Baca semua baris tabel | Jelek di tabel besar, wajar di tabel kecil |
Index Scan | Loncat langsung ke baris yang cocok lewat index | Bagus |
Index Only Scan | Semua kolom yang dibutuhin ada di index | Paling bagus |
Bitmap Heap Scan | Kumpulin lokasi baris dulu, baru ambil | Bagus kalau hasilnya sedang |
Nested Loop | Buat tiap baris tabel A, cari di tabel B | Bagus kalau tabel A kecil, bencana kalau besar |
Hash Join | Bikin tabel hash di memori, lalu cocokin | Bagus buat JOIN besar |
Merge Join | Dua tabel diurutin lalu digabung | Bagus kalau dua-duanya udah terurut |
Sort | Ngurutin hasil | Mahal kalau datanya gak muat memori |
Yang paling sering jadi biang lambat: Seq Scan di tabel jutaan baris, dan Nested Loop yang loops-nya ribuan.
Kalau kamu lihat Sort Method: external merge Disk: 84200kB, itu artinya sorting-nya gak muat di memori dan tumpah ke disk. Lambat. Tambah work_mem atau kurangi data yang di-sort.
Balik ke query toko_berkah tadi. Execution Time 8.142 ms.
Penyebabnya jelas dari plan: Seq Scan, dan 1,5 juta baris dibuang oleh filter tanggal. Artinya database baca semua baris cuma buat buang sebagian besarnya.
Solusinya: kasih index di kolom yang difilter.
CREATE INDEX idx_transaksi_tanggal ON transaksi (tanggal);
Jalanin EXPLAIN ANALYZE lagi:
HashAggregate (cost=15230.42..15230.47 rows=5 width=40)
(actual time=41.219..41.223 rows=5 loops=1)
Group Key: kota
-> Bitmap Heap Scan on transaksi (actual time=8.914..28.106 rows=831405 loops=1)
Recheck Cond: (tanggal >= '2026-01-01'::date)
-> Bitmap Index Scan on idx_transaksi_tanggal (actual time=8.301..8.301 rows=831405 loops=1)
Planning Time: 0.211 ms
Execution Time: 41.284 ms
Seq Scan hilang, diganti Bitmap Index Scan. Execution Time turun dari 8.142 ms jadi 41 ms.
Itu 198 kali lebih cepat. Dari satu baris CREATE INDEX yang aku tulis setelah baca query plan-nya, bukan sebelum.
Ini poin utamanya. Tanpa EXPLAIN ANALYZE, aku mungkin nebak-nebak, ngubah GROUP BY, atau nambah index di kolom yang salah.
Perintahnya beda-beda tergantung database:
| Database | Perintah |
|---|---|
| PostgreSQL | EXPLAIN ANALYZE SELECT ... |
| MySQL 8 | EXPLAIN ANALYZE SELECT ... |
| SQLite | EXPLAIN QUERY PLAN SELECT ... |
| BigQuery | Tab Execution details di UI |
| SQL Server | SET STATISTICS PROFILE ON atau tombol Execution Plan |
Di PostgreSQL, versi lengkapnya kasih info lebih banyak lagi:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT t.kota, p.kategori, SUM(t.total) AS omzet
FROM transaksi t
JOIN produk p ON p.id = t.produk_id
WHERE t.tanggal >= '2026-01-01'
GROUP BY t.kota, p.kategori
ORDER BY omzet DESC;
BUFFERS nunjukin berapa banyak data dibaca dari memori vs dari disk. Kalau angka read-nya besar, query kamu bolak-balik ke disk. Itu lambat.
Detail lengkap semua opsinya ada di dokumentasi resmi PostgreSQL.
Cost 44120 bukan berarti 44 detik. Itu satuan internal buat ngebandingin rencana. Kalau kamu mau angka waktu, pakai EXPLAIN ANALYZE dan lihat actual time.
Kalau query kamu emang butuh 80% baris tabel, Seq Scan justru lebih cepat dari Index Scan. Loncat-loncat lewat index buat 80% baris itu malah lebih mahal daripada baca berurutan.
Database sering udah bener soal ini. Yang perlu kamu waspadai: Seq Scan yang Rows Removed by Filter-nya jauh lebih besar dari rows yang keluar.
Habis import 2 juta baris, statistik tabel kamu masih ngira tabelnya kecil. Database bakal pilih rencana yang salah. Jalanin ANALYZE nama_tabel.
Index bikin SELECT cepat, tapi INSERT dan UPDATE jadi lambat, tiap tulis data, semua index-nya harus ikut diperbarui. Bikin index cuma di kolom yang sering muncul di WHERE, JOIN, atau ORDER BY.
Query plan di tabel 1.000 baris beda sama di tabel 2 juta baris. Database bakal pilih Seq Scan di tabel kecil dan kamu gak bakal lihat masalahnya. Tes di data yang ukurannya mirip production.
EXPLAIN ANALYZE.actual time paling besar.Seq Scan di tabel besar? Nested Loop dengan loops ribuan?Rows Removed by Filter. Kalau besar, kolom filternya butuh index.ANALYZE.Satu perubahan, satu pengukuran. Kalau kamu ubah tiga hal sekaligus, kamu gak tau mana yang berhasil.
Buat teknik perbaikannya, baca 10 teknik optimasi query SQL. Dan kalau kamu masih baru sama konsep index, mulai dari glosarium index.
EXPLAIN cuma nunjukin rencananya. EXPLAIN ANALYZE beneran jalanin query-nya dan ngasih waktu asli tiap langkah.
Buat SELECT, aman. Buat UPDATE atau DELETE, bahaya, datanya beneran berubah. Bungkus di BEGIN dan ROLLBACK.
Database baca semua baris tabel satu per satu. Wajar di tabel kecil, biang lambat di tabel jutaan baris.
Statistik tabelnya basi. Jalanin ANALYZE nama_tabel buat nyegerinnya.
Satuan internal database, bukan detik. Cuma berguna buat ngebandingin rencana satu sama lain.
Yang perlu kamu inget:
Execution Time dan actual time itu angka nyata. cost cuma perbandingan.Seq Scan + Rows Removed by Filter yang besar = kolom itu butuh index.Ambil query paling lambat di kerjaan kamu, tempel EXPLAIN ANALYZE di depannya, dan lihat baris mana yang actual time-nya paling gede. Lima menit, dan kamu udah tau di mana masalahnya.
Mau latihan nulis dan ngetes query langsung di browser? Coba NgulikSQL, gratis, gak perlu install database.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.