Optimasi Query SQL: 10 Teknik yang Langsung Kerasa Efeknya
TL;DR
Optimasi query SQL yang paling besar dampaknya biasanya cuma tiga hal: kasih index di kolom yang sering kamu filter, hindari SELECT bintang, dan filter data sebelum JOIN, bukan sesudah. Sebelum ngubah apa pun, jalanin EXPLAIN ANALYZE dulu buat lihat di mana waktunya kebuang. Ngubah query berdasarkan tebakan sering bikin lebih lambat, bukan lebih cepat.
Optimasi query SQL yang paling besar dampaknya biasanya cuma tiga hal: index di kolom yang sering difilter, berhenti pakai SELECT *, dan filter data sebelum JOIN.
Sisanya pelengkap. Tapi pelengkap yang kadang nyelametin kamu waktu tiga teknik pertama udah kepakai dan query-nya masih lambat.
Satu aturan sebelum mulai: jangan ubah apa pun sebelum kamu jalanin EXPLAIN ANALYZE. Ngoptimasi berdasarkan tebakan sering bikin lebih lambat, bukan lebih cepat.
1. Ambil kolom yang kamu butuh aja
SELECT * narik semua kolom, termasuk kolom deskripsi yang panjangnya 2 KB dan gak kamu pakai.
-- Lambat
SELECT * FROM transaksi WHERE tanggal >= '2026-01-01';
-- Cepat
SELECT id, kota, total FROM transaksi WHERE tanggal >= '2026-01-01';
Efeknya dua lapis. Pertama, data yang dibaca dari disk lebih sedikit. Kedua, dan ini yang lebih penting: SELECT * bikin Index Only Scan gak mungkin kepakai. Database terpaksa balik ke tabel buat ngambil kolom yang gak ada di index.
Di tabel transaksi toko_berkah yang punya 14 kolom, ganti dari SELECT * jadi 3 kolom bikin query turun dari 2,1 detik ke 780 ms. Cuma dari ini.
2. Kasih index di kolom yang sering difilter
Ini teknik dengan rasio dampak-per-usaha paling tinggi. Satu baris SQL, efeknya bisa ratusan kali lipat.
CREATE INDEX idx_transaksi_tanggal ON transaksi (tanggal);
Kolom yang layak dikasih index: yang muncul di WHERE, JOIN, atau ORDER BY, dan sering dipakai.
Buat query yang sering nyaring dua kolom sekaligus, bikin composite index:
CREATE INDEX idx_transaksi_kota_tanggal ON transaksi (kota, tanggal);
Urutan kolom di composite index itu penting. Index (kota, tanggal) kepakai buat filter kota doang, dan buat filter kota + tanggal. Tapi gak kepakai buat filter tanggal doang.
Aturannya: kolom yang paling sering difilter, taruh di depan.
3. Jangan bungkus kolom dengan fungsi di WHERE
Ini pembunuh index yang paling senyap.
-- Index mati
SELECT id, total FROM transaksi WHERE YEAR(tanggal) = 2026;
-- Index hidup
SELECT id, total FROM transaksi
WHERE tanggal >= '2026-01-01' AND tanggal < '2027-01-01';
Kenapa? Index nyimpen nilai tanggal, bukan nilai YEAR(tanggal). Begitu kamu bungkus kolomnya dengan fungsi, database gak bisa lagi nyari lewat index — dia harus hitung fungsi itu di tiap baris.
Sama juga buat teks:
-- Index mati
WHERE UPPER(kota) = 'JAKARTA'
-- Index hidup
WHERE kota = 'Jakarta'
Kalau kamu emang butuh pencarian tanpa peduli huruf besar-kecil, bikin index-nya di ekspresi itu:
CREATE INDEX idx_kota_upper ON transaksi (UPPER(kota));
4. Filter dulu, JOIN belakangan
Kalau kamu JOIN dua tabel besar lalu baru filter, database sering ngerjain gabungan yang jauh lebih besar dari yang kamu butuh.
WITH transaksi_2026 AS (
SELECT produk_id, kota, total
FROM transaksi
WHERE tanggal >= '2026-01-01'
)
SELECT p.kategori, t.kota, SUM(t.total) AS omzet
FROM transaksi_2026 t
JOIN produk p ON p.id = t.produk_id
GROUP BY p.kategori, t.kota;
Query planner yang bagus sering udah ngelakuin ini sendiri. Tapi "sering" bukan "selalu" — apalagi kalau statistik tabelnya basi.
Nulis eksplisit kayak gini juga bikin query kamu lebih gampang dibaca orang lain. Itu bonus yang gak keitung di benchmark.
5. Ganti correlated subquery dengan window function
Correlated subquery itu subquery yang jalan sekali per baris hasil. Kalau hasilnya 100 ribu baris, subquery itu jalan 100 ribu kali.
-- Lambat: subquery jalan tiap baris
SELECT
kota,
total,
(SELECT AVG(total) FROM transaksi t2 WHERE t2.kota = t1.kota) AS rata_kota
FROM transaksi t1
WHERE tanggal >= '2026-01-01';
-- Cepat: sekali baca, semua dihitung
SELECT
kota,
total,
AVG(total) OVER (PARTITION BY kota) AS rata_kota
FROM transaksi
WHERE tanggal >= '2026-01-01';
Di data toko_berkah dengan 2,4 juta baris, versi pertama makan 38 detik. Versi window function: 1,2 detik.
Kalau kamu belum familiar sama window function, mulai dari glosarium window function.
6. EXISTS lebih cepat dari IN buat subquery besar
-- Lambat kalau subquery-nya ngembaliin jutaan baris
SELECT id, nama FROM produk
WHERE id IN (SELECT produk_id FROM transaksi);
-- Cepat: berhenti begitu ketemu satu
SELECT p.id, p.nama
FROM produk p
WHERE EXISTS (
SELECT 1 FROM transaksi t WHERE t.produk_id = p.id
);
EXISTS berhenti nyari begitu nemu satu baris yang cocok. IN sering ngumpulin dulu semua hasil subquery-nya.
Bonus: kalau kolom di subquery bisa berisi NULL, NOT IN bakal ngasih hasil kosong yang bikin kamu bingung berjam-jam. NOT EXISTS gak punya masalah itu.
7. Pakai LIMIT waktu eksplorasi
Waktu kamu lagi ngintip data buat pertama kali, kamu gak butuh 2 juta baris.
SELECT id, kota, total FROM transaksi LIMIT 100;
Kedengeran sepele, tapi ini nyelametin kamu dari nge-hang bikin editor SQL kamu — dan nyelametin database production dari beban yang gak perlu.
Hati-hati satu hal: LIMIT tanpa ORDER BY gak ngasih jaminan baris mana yang keluar. Buat eksplorasi, gak masalah. Buat laporan, selalu pasangin sama ORDER BY.
8. Kurangi data yang di-sort
Sorting itu mahal. Kalau data yang di-sort gak muat di memori, database numpahin ke disk — dan itu lambat banget.
Tandanya kelihatan di query plan:
Sort Method: external merge Disk: 84200kB
Kata Disk di situ artinya sorting kamu tumpah. Dua cara benerinnya:
- Filter dulu sebelum
ORDER BY, biar data yang di-sort lebih sedikit. - Bikin index di kolom
ORDER BY, biar database bisa baca data yang udah terurut dan skip sorting-nya sama sekali.
9. Hindari SELECT DISTINCT sebagai penambal
Banyak orang nambahin DISTINCT waktu hasil query-nya ada duplikat. Padahal duplikat itu biasanya gejala dari JOIN yang salah.
-- Nambal gejala
SELECT DISTINCT p.nama FROM produk p
JOIN transaksi t ON t.produk_id = p.id;
-- Benerin akarnya
SELECT p.nama FROM produk p
WHERE EXISTS (SELECT 1 FROM transaksi t WHERE t.produk_id = p.id);
DISTINCT maksa database ngurutin atau bikin hash dari seluruh hasil buat buang duplikat. Itu kerjaan mahal yang sebenernya bisa dihindari.
Kalau kamu nemu DISTINCT di query kamu, tanya dulu: kenapa ada duplikat? Biasanya jawabannya ada di JOIN yang kena baris ganda.
10. Pakai materialized view buat query yang berulang
Query yang sama dijalanin 200 orang tiap pagi, hasilnya gak harus real-time, dan hitungnya mahal? Simpan hasilnya.
CREATE MATERIALIZED VIEW omzet_bulanan AS
SELECT
DATE_TRUNC('month', tanggal) AS bulan,
kota,
SUM(total) AS omzet
FROM transaksi
GROUP BY 1, 2;
Lalu refresh sekali sehari:
REFRESH MATERIALIZED VIEW omzet_bulanan;
200 orang itu sekarang baca hasil yang udah jadi. Query yang tadinya 12 detik jadi 30 ms.
Jangan pakai ini buat data yang harus akurat detik itu juga — stok barang, saldo, atau apa pun yang orang bakal ambil keputusan langsung dari situ.
Contoh kasus: dari 40 detik jadi 200 milidetik
Ini query laporan bulanan toko_berkah yang aslinya begini:
SELECT *
FROM transaksi t
JOIN produk p ON p.id = t.produk_id
JOIN kategori k ON k.id = p.kategori_id
WHERE YEAR(t.tanggal) = 2026
AND UPPER(t.kota) = 'JAKARTA'
ORDER BY t.total DESC;
Waktu eksekusi: 41,3 detik di tabel 2,4 juta baris.
EXPLAIN ANALYZE nunjukin Seq Scan di transaksi, dengan 2,3 juta baris dibuang oleh filter.
Perbaikannya, satu per satu:
- Ganti
SELECT *jadi 5 kolom yang beneran dipakai. → 28,7 detik - Ganti
YEAR(tanggal)jadi range tanggal. → 26,1 detik (belum ada index, jadi belum kerasa) - Ganti
UPPER(kota)jadikota = 'Jakarta'. → 25,8 detik - Bikin composite index
(kota, tanggal). → 0,89 detik - Bikin index di
produk_idbuat JOIN-nya. → 0,21 detik
Total: dari 41,3 detik jadi 210 ms. 197 kali lebih cepat.
Perhatiin langkah 4. Itu yang paling besar dampaknya — turun dari 25,8 detik ke 0,89 detik dalam satu langkah.
Tapi index itu cuma kepakai karena langkah 2 dan 3 udah ngelepas fungsi dari kolom WHERE. Kalau YEAR(tanggal) masih ada, index-nya gak bakal kesentuh sama sekali.
Itu poinnya. Teknik-teknik ini saling numpuk.
Kesalahan umum waktu optimasi
1. Ngubah beberapa hal sekaligus
Kamu ubah 4 hal, query jadi cepat, tapi kamu gak tau yang mana yang berhasil. Ubah satu, ukur, ubah lagi. Lebih lama sedikit, tapi kamu jadi ngerti.
2. Bikin index buat semua kolom
Index bikin SELECT cepat, tapi INSERT dan UPDATE lambat — tiap tulis data, semua index-nya ikut diperbarui. Di tabel yang sering ditulis, kebanyakan index bisa lebih merugikan dari yang kamu kira.
3. Ngetes di database kecil
Query plan di tabel 1.000 baris beda total sama di 2 juta baris. Database bakal pilih Seq Scan di tabel kecil dan kamu gak lihat masalahnya sama sekali.
4. Lupa jalanin ANALYZE
Habis import jutaan baris, statistik tabel kamu masih ngira tabelnya kecil. Query planner bakal salah pilih rencana.
ANALYZE transaksi;
5. Percaya angka pertama
Query kedua sering lebih cepat dari yang pertama gara-gara data udah masuk cache. Jalanin 3 kali, ambil yang tengah.
Urutan yang aku pakai
- Jalanin
EXPLAIN ANALYZE. Cari langkah denganactual timepaling besar. - Ada
Seq Scandi tabel besar denganRows Removed by Filteryang gede? Kolom itu butuh index. - Ada fungsi yang mbungkus kolom di
WHERE? Lepas dulu, baru index-nya kepakai. - Ganti
SELECT *jadi kolom yang beneran dipakai. - Ada correlated subquery? Ganti pakai JOIN atau window function.
- Ukur lagi. Ulangi dari langkah 1.
Referensi lengkap soal cara kerja query planner ada di dokumentasi resmi PostgreSQL.
FAQ
Kenapa query aku lambat padahal datanya gak banyak?
Biasanya ada fungsi di kolom WHERE yang matiin index, atau ada correlated subquery yang jalan berulang tiap baris. Jalanin EXPLAIN ANALYZE dan cari langkah paling lama.
SELECT * beneran bikin lambat?
Iya. Selain narik kolom yang gak kepakai, dia bikin Index Only Scan gak mungkin dipakai.
Berapa banyak index yang boleh dibikin?
Cuma di kolom yang sering muncul di WHERE, JOIN, atau ORDER BY. Tiap index bikin tulis data jadi lebih lambat.
Lebih cepat mana, JOIN atau subquery?
Mirip, kecuali correlated subquery — itu yang beneran lambat. Ganti pakai JOIN atau window function.
Kapan pakai materialized view?
Waktu query yang sama dipakai berulang, mahal, dan hasilnya gak harus real-time.
Penutup
Tiga hal yang paling sering nyelesaiin masalah:
- Index di kolom yang sering difilter — dampaknya paling besar per usaha.
- Lepas fungsi dari kolom di
WHERE— kalau gak, index kamu gak kepakai. EXPLAIN ANALYZEsebelum ngubah apa pun — biar kamu gak ngoptimasi bagian yang salah.
Ambil query paling lambat di kerjaan kamu minggu ini. Jalanin EXPLAIN ANALYZE, cari Seq Scan-nya, dan bikin satu index. Sepuluh menit, dan kemungkinan besar query itu jadi puluhan kali lebih cepat.
Mau latihan nulis query dan langsung lihat waktu eksekusinya? Coba NgulikSQL — gratis, langsung di browser, gak perlu install database.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel Terkait
7 SQL Client Gratis Terbaik buat Analis Data (2026)
Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.