Sebagian besar aplikasi web yang lambat bukan karena kode PHP-nya buruk, melainkan karena query databasenya tidak efisien. Satu query yang buruk bisa membuat halaman load 10 detik padahal logika aplikasinya sudah optimal. Artikel ini membahas teknik-teknik konkret yang langsung bisa diterapkan.

Alat Diagnosis Pertama: EXPLAIN

Sebelum mengoptimasi, Anda harus tahu query mana yang bermasalah dan mengapa. EXPLAIN adalah perintah MySQL yang menunjukkan execution plan query:

EXPLAIN SELECT p.id, p.title, u.name
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.status = 'published'
ORDER BY p.published_at DESC
LIMIT 20;

Kolom yang paling penting untuk diperhatikan:

  • type — Jenis join. Urutkan dari baik ke buruk: const > eq_ref > ref > range > index > ALL. Jika nilainya ALL, artinya full table scan — ini yang ingin dihindari.
  • rows — Estimasi jumlah baris yang diperiksa. Semakin kecil, semakin baik.
  • Extra — Keterangan tambahan. Using filesort dan Using temporary adalah warning tanda query perlu dioptimasi.

INDEX: Solusi Paling Impactful

Menambahkan index yang tepat adalah perubahan tunggal yang paling sering menghasilkan improvement performa dramatis. Aturan dasarnya:

Index Kolom di WHERE, JOIN, dan ORDER BY

-- Query yang sering dijalankan:
SELECT * FROM posts WHERE status = 'published' ORDER BY published_at DESC;

-- Index yang dibutuhkan (composite index):
CREATE INDEX idx_posts_status_published_at ON posts(status, published_at DESC);

-- Di Laravel migration:
$table->index(['status', 'published_at']);

Composite Index: Urutan Kolom Penting

Composite index (A, B, C) bisa digunakan untuk query yang memfilter A, A+B, atau A+B+C, tapi tidak bisa untuk query yang hanya memfilter B atau C saja (tanpa A). Letakkan kolom dengan selectivity tertinggi di posisi pertama.

Hindari Index Killer

-- ❌ Index tidak dipakai karena fungsi di kolom
WHERE YEAR(created_at) = 2025

-- ✅ Range yang bisa pakai index
WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31'

-- ❌ Leading wildcard tidak bisa pakai index
WHERE name LIKE '%ahmad%'

-- ✅ Trailing wildcard bisa pakai index
WHERE name LIKE 'ahmad%'

Optimasi N+1 di Laravel

N+1 adalah penyebab umum query yang membengkak. Aktifkan Laravel Debugbar atau Query Logger untuk mendeteksinya:

// Aktifkan query log di AppServiceProvider (hanya di dev)
DB::listen(function ($query) {
    if ($query->time > 100) {
        Log::warning('Slow query: ' . $query->sql, [
            'bindings' => $query->bindings,
            'time'     => $query->time,
        ]);
    }
});

// Perbaiki N+1 dengan eager loading
// ❌ N+1: 1 query posts + N query untuk setiap author
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // Query baru setiap iterasi
}

// ✅ 2 query total
$posts = Post::with('author')->get();

Pagination vs Get All

Jangan pernah load semua data dari tabel besar ke memori. Gunakan pagination atau chunk:

// ❌ Berbahaya untuk tabel besar
$posts = Post::all();

// ✅ Pagination
$posts = Post::paginate(20);

// ✅ Chunk untuk processing batch
Post::chunk(500, function ($posts) {
    foreach ($posts as $post) {
        // Process setiap batch 500 record
    }
});

Query Caching dengan Redis

Untuk query yang mahal dan hasilnya tidak sering berubah, cache hasilnya:

$stats = Cache::remember('dashboard.stats', now()->addMinutes(15), function () {
    return [
        'total_users' => User::count(),
        'total_posts' => Post::published()->count(),
        'today_views' => PageView::today()->sum('views'),
    ];
});

Konfigurasi InnoDB Buffer Pool

Untuk server production dedicated MySQL, konfigurasi ini sangat impactful. Set innodb_buffer_pool_size ke 70-80% RAM server:

# /etc/mysql/mysql.conf.d/mysqld.cnf
innodb_buffer_pool_size = 4G        # Untuk server 6GB RAM
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2  # Performance vs durability trade-off
query_cache_type = 0                # Deprecated di MySQL 8, disabled

Database optimization adalah proses iteratif. Mulai dari profiling untuk mengidentifikasi bottleneck, tambahkan index yang tepat, lalu ukur kembali. Seringkali satu atau dua perubahan index menghasilkan improvement yang jauh lebih besar dari refactoring kode besar-besaran.