Index database
Hampir semua masalah performa aplikasi Laravel yang serius berakhir di satu tempat: query yang memindai lebih banyak baris daripada yang diperlukan. Index adalah obatnya, dan membacanya lebih mudah daripada dugaan kebanyakan orang.
Intisari
- Index mempercepat
WHERE,JOIN, danORDER BYโ dengan biaya tulis yang sedikit lebih lambat. - Urutan kolom pada index gabungan sangat menentukan. Ikuti aturan awalan paling kiri.
EXPLAINadalah alat utamanya.type: ALLberarti pemindaian penuh tabel.- Index tidak terpakai kalau kolomnya dibungkus fungsi, atau kalau
LIKE-nya diawali%. - Covering index โ index yang memuat semua kolom yang dibutuhkan query โ menghilangkan pembacaan tabel sama sekali.
Membaca EXPLAIN
Artikel::where('status', 'terbit')
->where('kategori_id', 3)
->latest('terbit_pada')
->limit(20)
->explain()
->dd();
Kolom type | Artinya | Sehat? |
|---|---|---|
const / eq_ref | Satu baris lewat kunci unik | Sempurna |
ref | Beberapa baris lewat index | Baik |
range | Rentang nilai lewat index | Baik |
index | Seluruh index dipindai | Meragukan |
ALL | Seluruh tabel dipindai | Perbaiki |
Dua kolom lain yang sama pentingnya: rows (perkiraan jumlah baris yang diperiksa โ bandingkan
dengan jumlah baris yang akhirnya kamu kembalikan) dan Extra. Munculnya
Using filesort atau Using temporary berarti database mengurutkan di memori atau membuat
tabel sementara, dan biasanya berarti index untuk ORDER BY-nya belum ada.
Aturan awalan paling kiri
$table->index(['status', 'kategori_id', 'terbit_pada']);
| Query menyaring | Index terpakai? |
|---|---|
status | Ya |
status + kategori_id | Ya |
status + kategori_id + urut terbit_pada | Ya โ sepenuhnya |
kategori_id saja | Tidak โ melewati kolom paling kiri |
terbit_pada saja | Tidak |
Konsekuensinya: urutan kolom bukan selera. Susun dari kolom yang paling sering dipakai sebagai penyaring kesetaraan, lalu kolom rentang atau pengurutan di akhir. Satu index gabungan yang urutannya tepat sering menggantikan tiga index terpisah โ dan tiga index terpisah memperlambat setiap operasi tulis tanpa memberi manfaat sebanding.
Cara paling umum membuat index jadi sia-sia
// 1. Kolom dibungkus fungsi โ index pada terbit_pada tidak terpakai
Artikel::whereRaw('YEAR(terbit_pada) = ?', [2026])->get();
// Perbaikan: bandingkan rentang, biarkan kolomnya polos
Artikel::whereBetween('terbit_pada', ['2026-01-01', '2026-12-31 23:59:59'])->get();
// 2. LIKE yang diawali wildcard โ tidak ada awalan untuk ditelusuri
Artikel::where('judul', 'like', '%banjir%')->get();
// Perbaikan: full-text index, atau mesin pencari terpisah
Artikel::whereFullText('judul', 'banjir')->get();
// 3. Tipe yang tidak cocok โ konversi diam-diam mematikan index
Artikel::where('kode', 12345)->get(); // kolomnya VARCHAR
Artikel::where('kode', '12345')->get(); // benar
// 4. OR yang melintasi kolom berbeda
Artikel::where('judul', 'like', 'banjir%')->orWhere('ringkasan', 'like', 'banjir%')->get();
// Sering lebih cepat sebagai dua query yang digabung dengan union
Covering index
// Query hanya butuh empat kolom ini
Artikel::select('id', 'judul', 'slug', 'terbit_pada')
->where('status', 'terbit')
->latest('terbit_pada')
->limit(20)
->get();
$table->index(['status', 'terbit_pada', 'judul', 'slug'], 'artikel_daftar_idx');
Kalau seluruh kolom yang dibutuhkan sudah ada di dalam index, database tidak perlu membuka tabelnya sama sekali โ
EXPLAIN akan menunjukkan Using index. Untuk halaman daftar yang paling sering dibuka,
ini sering menjadi perbedaan antara 120 ms dan 4 ms.
SELECT * adalah musuh covering index. Ia memaksa database membaca setiap kolom termasuk
isi artikel yang panjang, padahal halaman daftar cuma menampilkan judul. Menyebutkan kolom secara eksplisit
pada query daftar adalah salah satu perubahan berdampak-tinggi yang paling murah.
Index untuk pola yang khas di situs konten
| Pola query | Index yang tepat |
|---|---|
| Artikel terbaru yang sudah terbit | (status, terbit_pada DESC) |
| Artikel per kategori, terbaru dulu | (kategori_id, status, terbit_pada DESC) |
| Cari lewat slug | unique(slug) |
| Komentar sebuah artikel | (artikel_id, created_at) |
| Arsip per bulan | (terbit_pada), disaring sebagai rentang |
| Pencarian teks | Full-text index, atau mesin pencari terpisah |
Biaya sebuah index
- Setiap
INSERT,UPDATE, danDELETEharus memperbarui setiap index. - Index memakan ruang disk dan ruang di memori buffer database.
- Index yang tidak pernah dipakai adalah biaya murni tanpa manfaat.
- Membuat index pada tabel besar membutuhkan waktu dan bisa mengunci โ rencanakan, jangan spontan.
-- MySQL: index mana yang tidak pernah terpakai
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema = 'nama_database';
Untuk beban baca yang berat, index dan read replica menyelesaikan masalah yang berbeda. Index membuat setiap query lebih murah; replica menambah kapasitas untuk menjalankan lebih banyak query. Menambah replica tanpa memperbaiki index berarti kamu menyalin masalah yang sama ke lebih banyak mesin โ dan membayar untuk keduanya.
Latihan: buat 200.000 artikel dengan factory, jalankan query daftar dengan filter status dan
kategori, lalu baca EXPLAIN-nya. Tambahkan index gabungan yang urutannya tepat, jalankan ulang,
dan bandingkan kolom rows sebelum dan sesudahnya.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.