7 Antipattern SQL yang Bikin Query Lambat
TL;DR
Antipattern SQL adalah pola penulisan query yang kelihatan jalan tapi bikin database kerja lebih berat dari perlunya, jadi query lambat pas datanya besar. Tujuh yang paling sering: SELECT bintang, fungsi di kolom filter, leading wildcard, subquery per baris, DISTINCT penutup JOIN salah, OR lintas kolom, dan OFFSET besar. Kebanyakan bisa dibenerin tanpa ganti hardware, cukup ubah pola query dan cek pakai EXPLAIN.
Antipattern SQL adalah pola penulisan query yang kelihatan jalan tapi bikin database kerja lebih berat dari yang perlu. Hasilnya query lambat, apalagi pas datanya udah jutaan baris.
Query yang jalan di 100 baris belum tentu jalan di 5 juta baris. Banyak query lambat bukan karena database-nya lemah, tapi karena polanya salah.
Ini 7 antipattern yang paling sering aku temuin, plus cara benerinnya. Contohnya pakai dataset toko_berkah biar kebayang.
1. SELECT * padahal cuma butuh sedikit kolom
SELECT * narik semua kolom, termasuk yang nggak kamu pakai. Database harus baca dan kirim data lebih banyak, dan index jadi susah kepakai penuh.
-- Antipattern
SELECT * FROM transaksi WHERE tanggal >= '2026-01-01';
-- Lebih baik
SELECT id, tanggal, total FROM transaksi WHERE tanggal >= '2026-01-01';
Sebut kolom yang kamu butuh. Selain lebih cepat, query-mu juga nggak jebol pas ada kolom baru ditambah ke tabel.
2. Fungsi di kolom yang di-filter
Kalau kamu bungkus kolom pakai fungsi di bagian WHERE, index di kolom itu jadi nggak kepakai. Database terpaksa scan semua baris.
-- Antipattern: index tanggal mati
SELECT * FROM transaksi WHERE YEAR(tanggal) = 2026;
-- Lebih baik: index tanggal kepakai
SELECT * FROM transaksi
WHERE tanggal >= '2026-01-01' AND tanggal < '2027-01-01';
Biarin kolomnya polos di sisi kiri. Pindahin perhitungan ke sisi kanan, ke nilai yang dibandingin.
3. Filter pakai leading wildcard
LIKE '%budi' dengan persen di depan bikin index nggak bisa dipakai. Database harus cek tiap baris satu per satu.
-- Antipattern
SELECT * FROM pelanggan WHERE nama LIKE '%budi%';
Kalau kamu sering nyari teks di tengah, mendingan pakai full text search. Itu dibikin khusus buat kasus ini, dan jauh lebih cepat dari LIKE dengan wildcard di depan.
4. Subquery berulang di dalam SELECT
Naruh subquery yang jalan sekali per baris di bagian SELECT itu berat. Kalau tabel utama punya 100.000 baris, subquery-nya jalan 100.000 kali.
-- Antipattern
SELECT
p.nama,
(SELECT COUNT(*) FROM transaksi t WHERE t.id_pelanggan = p.id) AS jml
FROM pelanggan p;
-- Lebih baik: JOIN sekali, agregasi sekali
SELECT p.nama, COUNT(t.id) AS jml
FROM pelanggan p
LEFT JOIN transaksi t ON t.id_pelanggan = p.id
GROUP BY p.nama;
Ganti jadi JOIN plus GROUP BY. Database ngerjain agregasinya sekali, bukan per baris.
5. DISTINCT buat nutupin JOIN yang salah
Kalau hasil query kamu dobel, terus kamu tambahin DISTINCT biar rapi, itu tanda bahaya. Biasanya JOIN-nya yang salah dan bikin baris berlipat.
DISTINCT maksa database ngurutin dan buang duplikat, dan itu mahal. Benerin dulu relasi JOIN-nya. Cari kenapa barisnya dobel, jangan tutupin gejalanya.
6. OR yang bikin index nggak kepakai
Kondisi OR di dua kolom beda sering bikin database nyerah pakai index dan milih scan penuh.
-- Antipattern
SELECT * FROM transaksi
WHERE id_pelanggan = 12 OR id_produk = 5;
-- Lebih baik: dua query digabung UNION
SELECT * FROM transaksi WHERE id_pelanggan = 12
UNION
SELECT * FROM transaksi WHERE id_produk = 5;
Pecah jadi dua query lewat UNION. Tiap bagian bisa pakai index-nya sendiri. Nggak selalu perlu, tapi berguna pas OR-nya lintas kolom.
7. Pagination pakai OFFSET besar
OFFSET 100000 tetap maksa database baca dan buang 100.000 baris pertama sebelum ngasih halaman yang kamu mau. Makin dalam halamannya, makin lambat.
-- Antipattern buat halaman dalam
SELECT * FROM transaksi ORDER BY id LIMIT 20 OFFSET 100000;
-- Lebih baik: keyset pagination
SELECT * FROM transaksi WHERE id > 100000 ORDER BY id LIMIT 20;
Pakai keyset pagination, yaitu nyaring dari nilai terakhir yang udah kamu lihat. Aku bahas lengkap di artikel pagination keyset.
Contoh kasus: query dashboard toko_berkah
toko_berkah punya tabel transaksi 2,3 juta baris. Dashboard hariannya narik total penjualan per pelanggan dan awalnya pakai subquery per baris kayak antipattern nomor 4.
Query lama jalan 14 detik. Aku ganti subquery jadi JOIN plus GROUP BY, dan waktunya turun ke 1,2 detik. Nggak nambah index, cuma benerin pola query.
| Pola query | Waktu jalan |
|---|---|
| Subquery per baris | 14 detik |
| JOIN + GROUP BY | 1,2 detik |
Angka ini dari dataset ngulikdata di satu mesin, jadi bukan patokan mutlak. Tapi selisih sebesar itu sering muncul begitu subquery per baris dibuang.
FAQ
Gimana cara tau query-ku kena antipattern?
Jalanin EXPLAIN atau EXPLAIN ANALYZE di depan query kamu. Perintah ini nunjukin rencana kerja database, termasuk apakah dia pakai index atau scan seluruh tabel. Kalau kamu lihat kata "Seq Scan" atau "Full Table Scan" di tabel besar, biasanya ada pola yang bisa diperbaiki.
Apakah SELECT * selalu buruk?
Nggak selalu. Buat eksplorasi cepat atau tabel kecil, SELECT * aman dan praktis. Yang jadi masalah itu di query produksi yang jalan sering, apalagi tabelnya lebar dan besar. Di situ kolom yang nggak kepakai bikin transfer data lebih berat. Kebiasaan sebut kolom juga bikin query lebih tahan perubahan skema.
Apakah antipattern ini sama di semua database?
Sebagian besar iya. Prinsip index, JOIN, dan OFFSET mirip di PostgreSQL, MySQL, dan SQL Server. Detail kecilnya bisa beda, misalnya nama fungsi atau cara optimizer milih rencana. Kalau ragu, cek dokumentasi database yang kamu pakai dan selalu ukur pakai EXPLAIN di data aslimu.
Mana antipattern yang paling sering bikin lambat?
Dari yang aku temuin, subquery per baris dan fungsi di kolom filter paling sering jadi biang lambat. Keduanya diam-diam bikin database kerja berkali lipat tanpa keliatan di query yang pendek. Cek dua hal itu duluan sebelum yang lain, biasanya di situ untung terbesarnya.
Penutup
Kebanyakan query lambat bukan soal hardware, tapi pola. Tujuh hal di atas yang paling sering bikin masalah, dan semuanya bisa dibenerin tanpa ganti server.
Langkah pertama yang paling murah: jalanin EXPLAIN, lihat mana yang scan penuh, benerin satu per satu.
Mau lanjut? Baca pagination keyset vs OFFSET buat halaman dalam, dan composite index biar filter kamu kepakai index. Cek juga glosarium index dan halaman EXPLAIN. Panduan resminya ada di dokumentasi PostgreSQL.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel terkait
Semi Join dan Anti Join di SQL: Filter Pakai Tabel Lain
Semi join ngambil baris yang punya pasangan di tabel lain, anti join ngambil yang nggak punya. Keduanya filter, bukan gabung kolom. Ini cara nulisnya di SQL pakai EXISTS dan NOT EXISTS.
EXISTS vs IN di SQL: Perbedaan dan Performa (2026)
EXISTS dan IN sama-sama nyaring baris pakai subquery, tapi cara kerja dan performanya beda. Ini panduan milih yang tepat plus jebakan NULL-nya.
Crosstab (Pivot) di PostgreSQL: Ubah Baris Jadi Kolom
Crosstab di PostgreSQL mivot data dari format panjang (baris) jadi format lebar (kolom), mirip pivot table Excel. Butuh extension tablefunc, dan ada alternatif pakai CASE atau FILTER kalau kolomnya tetap.