โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 7 ยท Performa & Skala

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.

Sumber asli dev.mysql.com Resmi Rangkuman ~8 menit baca

Intisari

  • Index mempercepat WHERE, JOIN, dan ORDER BY โ€” dengan biaya tulis yang sedikit lebih lambat.
  • Urutan kolom pada index gabungan sangat menentukan. Ikuti aturan awalan paling kiri.
  • EXPLAIN adalah alat utamanya. type: ALL berarti 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 typeArtinyaSehat?
const / eq_refSatu baris lewat kunci unikSempurna
refBeberapa baris lewat indexBaik
rangeRentang nilai lewat indexBaik
indexSeluruh index dipindaiMeragukan
ALLSeluruh tabel dipindaiPerbaiki

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 menyaringIndex terpakai?
statusYa
status + kategori_idYa
status + kategori_id + urut terbit_padaYa โ€” sepenuhnya
kategori_id sajaTidak โ€” melewati kolom paling kiri
terbit_pada sajaTidak

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 queryIndex yang tepat
Artikel terbaru yang sudah terbit(status, terbit_pada DESC)
Artikel per kategori, terbaru dulu(kategori_id, status, terbit_pada DESC)
Cari lewat slugunique(slug)
Komentar sebuah artikel(artikel_id, created_at)
Arsip per bulan(terbit_pada), disaring sebagai rentang
Pencarian teksFull-text index, atau mesin pencari terpisah

Biaya sebuah index

  1. Setiap INSERT, UPDATE, dan DELETE harus memperbarui setiap index.
  2. Index memakan ruang disk dan ruang di memori buffer database.
  3. Index yang tidak pernah dipakai adalah biaya murni tanpa manfaat.
  4. 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.