MySQL untuk Developer: Index, JOIN, dan Optimasi Query dalam Satu Artikel
Kalau ada satu skill yang dampaknya paling langsung terasa di performa aplikasi web, saya akan bilang: kemampuan menulis query MySQL yang efisien. Bukan karena hal lain tidak penting, tapi karena database sering jadi bottleneck yang tersembunyi β aplikasi kelihatan lambat, dan semua orang menyalahkan server atau framework, padahal masalahnya ada di query yang tidak optimal.
Saya belajar ini dengan cara yang cukup menyakitkan: halaman yang loading 5 detik, pengguna yang complain, dan proses debug yang memakan waktu berjam-jam sebelum akhirnya menemukan bahwa akar masalahnya adalah query tanpa index di tabel yang sudah ratusan ribu baris.
Memahami Index: Mengapa Query Bisa Tiba-tiba Cepat
Index di MySQL bekerja seperti indeks di buku. Tanpa indeks, MySQL harus baca semua baris satu per satu untuk nemuin data yang dicari β disebut full table scan. Dengan index, MySQL bisa langsung loncat ke lokasi data yang relevan.
Tiga jenis index yang paling sering dipakai:
1. Primary Key Index β otomatis dibuat di kolom PRIMARY KEY. Sudah ada di semua tabel yang dibuat dengan benar.
2. Single Column Index β untuk kolom yang sering dipakai di WHERE:
ALTER TABLE users ADD INDEX idx_email (email);
ALTER TABLE posts ADD INDEX idx_status (status);
3. Composite Index β untuk kombinasi kolom yang sering diquery bersamaan:
-- Kalau sering query: WHERE status = ? AND created_at > ?
ALTER TABLE posts ADD INDEX idx_status_date (status, created_at);
Urutan kolom di composite index itu penting. Index (status, created_at) efektif untuk query yang filter status saja, atau filter status dan created_at bersamaan. Tapi tidak efektif kalau hanya filter created_at tanpa status.
Untuk cek apakah query sudah menggunakan index, pakai EXPLAIN:
EXPLAIN SELECT * FROM posts WHERE status = "published" AND created_at > "2025-01-01";
Kolom key di output menunjukkan index yang dipakai. Kolom rows menunjukkan estimasi berapa baris yang di-scan β semakin kecil, semakin baik.
JOIN: Ambil Data dari Beberapa Tabel Sekaligus
Misalnya ada tabel posts dan users. Setiap post punya user_id yang merujuk ke tabel users.
INNER JOIN β hanya data yang punya pasangan di kedua tabel:
SELECT p.title, p.created_at, u.nama as penulis
FROM posts p
INNER JOIN users u ON u.id = p.user_id
WHERE p.status = "published"
ORDER BY p.created_at DESC
LIMIT 10;
LEFT JOIN β semua data dari tabel kiri, meski tidak ada pasangan di tabel kanan. Cocok untuk menampilkan posts beserta jumlah komentarnya, termasuk post yang belum ada komentar:
SELECT p.id, p.title,
COUNT(c.id) as jumlah_komentar
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
WHERE p.status = "published"
GROUP BY p.id, p.title
ORDER BY p.created_at DESC;
Satu hal yang wajib diperhatikan: kolom yang dipakai sebagai kondisi JOIN harus punya index. Di contoh di atas, posts.user_id, comments.post_id harus diindex:
ALTER TABLE posts ADD INDEX idx_user_id (user_id);
ALTER TABLE comments ADD INDEX idx_post_id (post_id);
Tanpa index di kolom JOIN, MySQL harus full scan untuk setiap baris β yang bisa sangat lambat di tabel besar.
Optimasi Query: Kebiasaan yang Harus Ditanamkan
1. Hindari SELECT *
Selalu spesifik dengan kolom yang dibutuhkan. SELECT * mengambil semua kolom meski tidak semuanya dipakai β termasuk kolom dengan teks panjang yang memakan bandwidth dan memory:
-- Buruk
SELECT * FROM posts WHERE status = "published";
-- Baik
SELECT id, title, slug, created_at FROM posts WHERE status = "published";
2. Hindari Query di Dalam Loop (N+1 Problem)
-- Pola yang bermasalah (PHP)
$posts = $db->query("SELECT * FROM posts")->fetchAll();
foreach ($posts as $post) {
// Query baru untuk setiap post!
$komentar = $db->query("SELECT * FROM comments WHERE post_id = {$post["id"]}")->fetchAll();
}
-- Solusi: JOIN atau ambil semua sekaligus
$postIds = array_column($posts, "id");
$placeholder = implode(",", array_fill(0, count($postIds), "?"));
$stmt = $db->prepare("SELECT * FROM comments WHERE post_id IN ($placeholder)");
$stmt->execute($postIds);
$semuaKomentar = $stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC, "post_id");
3. Pakai LIMIT untuk Semua Query yang Bisa Besar
-- Jangan ambil semua sekaligus
SELECT id, title FROM posts;
-- Selalu batasi
SELECT id, title FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 0;
4. Hindari Fungsi di Kolom WHERE
-- Buruk: MySQL tidak bisa pakai index di kolom yang dibungkus fungsi
WHERE YEAR(created_at) = 2025
-- Baik: gunakan range
WHERE created_at BETWEEN "2025-01-01" AND "2025-12-31 23:59:59"
Tools untuk Identifikasi Query Lambat
MySQL punya fitur Slow Query Log yang sangat berguna untuk menemukan query bermasalah:
# Di /etc/mysql/mysql.conf.d/mysqld.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
Setiap query yang eksekusinya lebih dari 1 detik akan dicatat di file tersebut. Dari situ bisa diidentifikasi mana yang perlu dioptimasi.
Penutup
Optimasi database itu bukan sesuatu yang dilakukan sekali di awal lalu selesai. Seiring data yang terus bertambah, query yang tadinya cepat bisa mulai melambat. Jadikan EXPLAIN sebagai kebiasaan setiap kali menulis query baru, aktifkan slow query log di production, dan review secara berkala.
Database yang diurus dengan baik itu salah satu investasi terbaik yang bisa dilakukan untuk performa aplikasi jangka panjang. π
Belum ada komentar. Jadilah yang pertama menulis.
Tulis Komentar