MySQL performance tuning untuk developer dimulai dari query, bukan dari konfigurasi server. Urutannya: temukan query yang lambat, baca rencana eksekusinya dengan EXPLAIN, tambahkan atau perbaiki index, lalu ubah pola query yang boros. Langkah-langkah ini biasanya memberi percepatan jauh lebih besar daripada menambah RAM atau CPU. Contoh di bawah berlaku untuk MySQL 8.
Langkah 1: Temukan Query yang Lambat
Slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5; -- catat query di atas 0,5 detik
SHOW VARIABLES LIKE 'slow_query_log_file';
Rangkum isinya dengan mysqldumpslow atau pt-query-digest untuk melihat query mana yang paling sering dan paling lama.
Dari sisi aplikasi Laravel
// AppServiceProvider::boot() di environment development
DB::listen(function (QueryExecuted $query) {
if ($query->time > 200) {
logger()->warning('Query lambat', ['sql' => $query->sql, 'ms' => $query->time]);
}
});
Laravel Debugbar atau Telescope juga menampilkan jumlah dan durasi query per halaman. Halaman yang menjalankan ratusan query biasanya kena masalah N+1, dibahas di Laravel 12 best practices.
Langkah 2: Baca EXPLAIN
EXPLAIN SELECT id, number, total
FROM orders
WHERE customer_id = 42 AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
| Kolom | Yang perlu diperhatikan |
|---|---|
type | ALL berarti full table scan (buruk untuk tabel besar). Targetkan ref, range, atau const. |
key | Index yang dipakai. NULL berarti tidak ada index yang dipakai. |
rows | Perkiraan baris yang dibaca. Semakin kecil semakin baik. |
Extra | Using filesort atau Using temporary menandakan pengurutan atau tabel sementara yang mahal. |
EXPLAIN ANALYZE di MySQL 8 menjalankan query sungguhan dan menampilkan waktu aktual setiap langkah.
Langkah 3: Index yang Tepat
Composite index mengikuti pola query
Untuk query di atas, satu index gabungan lebih efektif daripada tiga index terpisah:
CREATE INDEX orders_customer_status_created
ON orders (customer_id, status, created_at);
Aturan urutan kolom yang praktis: kolom yang difilter dengan kesamaan (=) di depan, kolom rentang atau pengurutan di belakang. Index (customer_id, status, created_at) bisa dipakai untuk filter customer_id saja, tapi tidak untuk filter status saja.
Di migration Laravel:
$table->index(['customer_id', 'status', 'created_at']);
Covering index
Kalau semua kolom yang dibutuhkan query ada di index, MySQL tidak perlu membaca tabel sama sekali. EXPLAIN akan menampilkan Using index.
Jangan berlebihan
Setiap index mempercepat baca tapi memperlambat insert dan update, serta memakan disk. Hapus index yang tidak pernah dipakai; MySQL 8 menyediakan view sys.schema_unused_indexes untuk membantu menemukannya.
Langkah 4: Pola Query yang Boros
| Pola | Masalah | Perbaikan |
|---|---|---|
WHERE DATE(created_at) = '2026-11-01' | Fungsi pada kolom mematikan index | WHERE created_at >= '2026-11-01' AND created_at < '2026-11-02' |
WHERE name LIKE '%budi%' | Wildcard di depan tidak bisa pakai index biasa | Full-text index atau mesin pencari |
SELECT * | Membaca kolom besar yang tidak perlu | Pilih kolom seperlunya |
OFFSET 100000 | MySQL tetap membaca 100.000 baris lalu membuangnya | Pagination berbasis kursor: WHERE id < ? ORDER BY id DESC LIMIT 20 |
Kolom VARCHAR dibandingkan dengan angka | Konversi tipe implisit, index tidak dipakai | Samakan tipe data |
Di Laravel, pagination kursor tersedia lewat cursorPaginate().
Langkah 5: MySQL Performance Tuning di Sisi Server
Setelah query dan index rapi, cek beberapa setting InnoDB:
innodb_buffer_pool_size: memori untuk cache data dan index. Di server khusus database, umumnya 50 sampai 70 persen RAM.max_connections: sesuaikan dengan jumlah worker PHP-FPM dan queue, jangan dibuat sangat besar tanpa alasan.- Pantau rasio cache hit dan jumlah koneksi. Kalau buffer pool terlalu kecil, MySQL sering membaca dari disk.
Untuk data yang sering dibaca tapi jarang berubah, tambahkan cache di level aplikasi seperti dijelaskan di Redis cache layer di Laravel.
Kesalahan yang Sering Terjadi
- Menambah index tanpa melihat EXPLAIN, sehingga index yang dibuat tidak pernah dipakai query.
- Menguji dengan data kecil. Query yang cepat di 500 baris bisa lambat di 5 juta baris. Uji dengan data seukuran production.
- Mengubah setting server dulu. Setting yang tepat tidak bisa menyelamatkan query tanpa index.
- Menambah index saat jam sibuk di tabel besar tanpa memeriksa apakah operasi itu mengunci tabel.
Langkah Berikutnya
Kalau konsep dasar seperti JOIN, primary key, dan normalisasi masih samar, mulai dari belajar database untuk pemula. Untuk aplikasi yang melayani banyak klien di satu database, perhatikan juga desain index di SaaS multi-tenant dengan Laravel.