Gratis update & dukungan instalasi

Panduan 05 Oct 2026

Cara Optimasi Database MySQL agar Website Tidak Lemot (Panduan Praktis 2026)

R Oleh renz mobellgnd
Cara Optimasi Database MySQL agar Website Tidak Lemot (Panduan Praktis 2026)

Cara Optimasi Database MySQL agar Website Tidak Lemot (Panduan Praktis 2026)

Website Anda masih kencang saat baru saja online, tapi makin lama makin lemot — apalagi setelah data transaksi, produk, atau pengguna bertambah banyak? Hampir selalu biang keladinya ada di database. Pada artikel 10 cara mempercepat website Laravel sebelumnya kita sudah membahas optimasi secara umum; kali ini kita bedah khusus optimasi database MySQL — jantung dari hampir semua aplikasi web, termasuk source code Laravel yang Anda beli di Kios Koding.

Anggap saja MySQL sebagai mesin mobil. Source code yang bagus adalah bodi yang aerodinamis, tapi kalau mesinnya (database) tidak pernah diservis, mobil tetap ngos-ngosan di tanjakan. Panduan ini membawa Anda dari diagnosa sampai solusi nyata, dengan contoh kode yang bisa langsung dipraktikkan.

Kenapa Database Bisa Bikin Website Lemot?

Beberapa gejala umum yang menandakan masalahnya ada di database:

  1. Halaman daftar produk/laporan butuh waktu > 3 detik untuk terbuka
  2. Dashboard admin makin berat setiap bulan berjalan
  3. Error 504 Gateway Timeout saat menarik laporan periode panjang
  4. CPU atau RAM server melonjak tiap ada banyak pengunjung

Penyebab klasiknya: tabel yang tidak punya index pada kolom yang sering dicari, query yang menarik seluruh kolom padahal butuhnya sedikit, dan pola ORM yang tanpa sadar mengeksekusi ratusan query untuk satu halaman. Kabar baiknya: optimasi database adalah jenis optimasi dengan dampak terbesar — satu index yang tepat bisa mempercepat query ratusan kali lipat.

⚠️ Catatan penting: sebelum mengutak-atik struktur database, selalu backup dulu. Kalau Anda baru membeli source code, ikuti dulu 9 langkah mengamankan website setelah beli source code — termasuk membuat backup dan mengganti kredensial bawaan.

Langkah 1: Diagnosa Dulu, Jangan Tebak-tebakan

Aturan pertama optimasi: ukur dulu, perbaiki setelahnya. Optimasi tanpa data hanya menghasilkan tebak-tebakan.

Gunakan EXPLAIN untuk Membaca "Pikiran" MySQL

Perintah EXPLAIN menunjukkan bagaimana MySQL mengeksekusi sebuah query — apakah ia memakai index atau memindai seluruh tabel.

EXPLAIN SELECT * FROM orders
WHERE status = 'completed' AND created_at > '2026-01-01';

Perhatikan dua kolom hasil EXPLAIN:

  1. type: ALL → MySQL memindai seluruh baris tabel (full table scan). Ini tanda bahaya.
  2. type: range, ref, atau eq_ref → MySQL memakai index. Ini yang kita mau.
  3. rows → perkiraan jumlah baris yang dipindai. Semakin kecil, semakin baik.
  4. Extra: Using filesort → MySQL harus mengurutkan hasil secara manual karena index tidak membantu.

Setelah membuat perbaikan (langkah berikutnya), jalankan EXPLAIN lagi dan bandingkan angkanya. Kalau rows turun dari 1.000.000 menjadi 48, optimasi Anda berhasil.

Aktifkan Slow Query Log

Slow query log mencatat semua query yang eksekusinya di atas ambang tertentu (misalnya 2 detik). Di shared hosting, fitur ini biasanya bisa diakses lewat phpMyAdmin atau panel hosting. Di VPS, aktifkan dengan:

SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 2;

Daftar query yang muncul di log inilah daftar "tersangka" yang wajib dioptimasi duluan.

Pantau Query di Laravel dengan Debugbar

Kalau aplikasinya Laravel (seperti kebanyakan source code di Kios Koding), pasang Laravel Debugbar di lingkungan development. Ia menampilkan jumlah query per halaman, total waktu eksekusinya, dan menandai query yang terduplikasi. Halaman yang mengeksekusi 200+ query untuk satu tampilan adalah kandidat utama untuk dioptimasi — biasanya ini kasus N+1 yang akan kita bahas di Langkah 4.

Langkah 2: Indexing — Senjata Paling Ampuh

Index bekerja seperti daftar isi buku: tanpa daftar isi, Anda harus membaca seluruh buku untuk menemukan satu bab. Dengan daftar isi, Anda langsung melompat ke halaman yang tepat. Tanpa index, MySQL memindai baris demi baris; dengan index, ia melompat langsung.

Kolom Mana yang Perlu Di-index?

Berikan index pada kolom yang sering muncul di:

  1. WHERE (kolom filter seperti status, email, category_id)
  2. JOIN (kolom foreign key seperti user_id, product_id)
  3. ORDER BY (kolom pengurutan seperti created_at)

Contoh di migration Laravel:

Schema::table('orders', function (Blueprint $table) {
$table->index('status');
$table->index('email');
$table->index(['user_id', 'created_at']); // composite index
});

Atau langsung via SQL (misalnya lewat tab SQL di phpMyAdmin):

CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);

Composite Index: Urutan Itu Penting

Composite index (gabungan beberapa kolom) jauh lebih ampuh untuk query filter bertingkat, tapi urutan kolomnya menentukan. Misalnya index (category_id, status, created_at):

  1. Query WHERE category_id = 5 → index terpakai ✅
  2. Query WHERE category_id = 5 AND status = 'active' → index terpakai ✅
  3. Query WHERE status = 'active' → index TIDAK terpakai ❌ (kolom pertama dilewati)

Aturannya: urutkan kolom dari yang paling sering difilter (kiri) ke yang lebih jarang (kanan), dan samakan dengan urutan kondisi di query tersering Anda.

Jangan Asal Menumpuk Index

Index bukan gratis. Setiap operasi INSERT, UPDATE, dan DELETE memaksa MySQL memperbarui semua index tabel tersebut. Tabel dengan 15 index bisa membuat proses input data melambat. Prinsipnya: buat index hanya untuk kolom yang benar-benar sering dicari, dan hapus index yang tidak pernah dipakai. Perintah SHOW INDEX FROM orders; membantu mengaudit index yang sudah ada.

Langkah 3: Tulis Query yang Lebih Efisien

Banyak query lambat bukan karena data besar, tapi karena penulisannya boros. Perbaiki kebiasaan ini:

1. Hindari SELECT *

-- Boros: mengambil semua kolom, termasuk yang tidak dipakai
SELECT * FROM products WHERE category_id = 3;

-- Efisien: ambil hanya yang dibutuhkan
SELECT id, name, price, image FROM products WHERE category_id = 3;

Di Laravel: Product::select('id', 'name', 'price')->where('category_id', 3)->get();. Perbedaannya terasa besar pada tabel lebar dengan kolom teks panjang.

2. Pilih JOIN yang Tepat, Hindari Subquery Berlapis

Subquery dalam klausa WHERE ... IN (...) sering dieksekusi berulang oleh optimizer. JOIN umumnya lebih efisien:

-- Kurang efisien
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'completed');

-- Lebih efisien
SELECT DISTINCT u.* FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'completed';

Dan kalau data di kedua tabel memang selalu ada, INNER JOIN lebih cepat daripada LEFT JOIN karena optimizer punya lebih banyak ruang untuk memilih rencana eksekusi terbaik.

3. Selalu Batasi dengan LIMIT dan Pagination

Jangan pernah menampilkan 10.000 baris sekaligus. Gunakan LIMIT di SQL mentah dan paginate() di Laravel:

$orders = Order::where('status', 'completed')->paginate(20);

4. Cache Hasil Query yang Berat

Laporan bulanan atau statistik dashboard tidak berubah setiap detik. Simpan hasilnya di cache:

$stats = Cache::remember('dashboard-stats', 3600, function () {
return Order::whereMonth('created_at', now()->month)
->selectRaw('status, COUNT(*) as total, SUM(total) as omzet')
->groupBy('status')->get();
});

Query berat cukup dieksekusi sekali per jam, sisanya disajikan dari cache. Ini teknik termudah dengan dampak instan.

Langkah 4: Perbaiki Pola Eloquent yang Bikin N+1 Query

Ini jebakan paling umum di aplikasi Laravel — termasuk pada source code siap pakai yang belum dioptimasi. Contoh klasik:

// BURUK: 1 query untuk orders + 1 query PER order = N+1 query
$orders = Order::all();
foreach ($orders as $order) {
echo $order->customer->name; // query baru di setiap loop!
}

Untuk 100 order, Laravel mengeksekusi 101 query. Solusinya adalah eager loading:

// BAIK: hanya 2 query, seberapa pun jumlah ordernya
$orders = Order::with('customer')->get();
foreach ($orders as $order) {
echo $order->customer->name; // tanpa query tambahan
}

Variasinya yang tak kalah penting:

  1. Order::withCount('items') — menghitung jumlah relasi langsung di database, tanpa me-load datanya.
  2. Order::with(['customer:id,name']) — batasi kolom relasi yang di-load.
  3. User::whereHas('orders', fn($q) => $q->where('status', 'completed')) — filter berdasarkan relasi tanpa memfilter manual di PHP.

Setelah membeli source code, audit halaman-halaman berat (daftar transaksi, laporan, dashboard) dengan Debugbar dan ubah pola N+1 menjadi eager loading. Sering kali ini saja sudah memangkas waktu loading dari 5 detik menjadi di bawah 1 detik.

Langkah 5: Hindari Kebiasaan yang Membuat Index Sia-sia

Index yang sudah dibuat dengan susah payah bisa diabaikan MySQL kalau query-nya ditulis sembarangan:

1. Jangan bungkus kolom ter-index dengan fungsi

-- Index pada created_at TIDAK terpakai (full table scan)
SELECT * FROM orders WHERE YEAR(created_at) = 2026;

-- Index terpakai
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';

2. Jangan pakai wildcard di awal pencarian teks

WHERE name LIKE '%budi%' tidak bisa memakai index B-Tree standar karena huruf awalnya tidak diketahui. Kalau aplikasi butuh pencarian teks serius, gunakan FULLTEXT index:

ALTER TABLE products ADD FULLTEXT INDEX ft_name_desc (name, description);
SELECT * FROM products WHERE MATCH(name, description) AGAINST('kopi susu');

3. Samakan tipe data saat JOIN dan perbandingan

Membandingkan kolom INT dengan string '5' (atau sebaliknya) memaksa MySQL melakukan konversi tipe di setiap baris — index pun diabaikan. Pastikan tipe data kolom dan nilai yang dibandingkan konsisten.

Langkah 6: Rawat Tabel dan Sesuaikan Konfigurasi

OPTIMIZE TABLE untuk Tabel yang Sering Diubah

Tabel yang sering di-UPDATE/DELETE (misalnya tabel log, sesi, atau keranjang) lama-lama terfragmentasi seperti hard disk. Jalankan secara berkala (misalnya sebulan sekali di jam sepi):

OPTIMIZE TABLE sessions, logs, carts;

Pilih Engine yang Tepat

Pastikan tabel memakai InnoDB, bukan MyISAM. InnoDB mendukung row-level locking (transaksi bersamaan tidak saling mengunci seluruh tabel) dan crash recovery yang jauh lebih baik. Cek dengan SHOW TABLE STATUS; dan konversi bila perlu: ALTER TABLE nama_tabel ENGINE = InnoDB;.

Sesuaikan dengan Kapasitas Hosting

Di VPS, parameter terpenting adalah innodb_buffer_pool_size — idealnya 50–70% dari total RAM server, karena ini adalah "memori kerja" MySQL. Di shared hosting Anda biasanya tidak bisa mengubahnya (dan RAM-nya pun berbagi dengan pengguna lain). Kalau website sudah dioptimasi tapi tetap lemot di shared hosting, itu sinyal kuat untuk naik kelas ke VPS — seperti dibahas di panduan deploy Laravel ke shared hosting, shared hosting memang punya batas wajar untuk aplikasi yang datanya terus bertumbuh.

Langkah 7: Checklist Optimasi 15 Menit

Kalau waktu Anda terbatas, kerjakan urutan ini — dari dampak terbesar:

  1. ✅ Backup database dulu (wajib sebelum perubahan apa pun)
  2. ✅ Pasang Laravel Debugbar, catat halaman dengan query terbanyak
  3. ✅ Aktifkan slow query log, catat query paling lambat
  4. ✅ Perbaiki N+1 query dengan eager loading (with, withCount)
  5. ✅ Tambahkan index pada kolom WHERE/JOIN/ORDER BY tersering (uji dengan EXPLAIN sebelum–sesudah)
  6. ✅ Ganti SELECT * dengan kolom spesifik pada query berat
  7. ✅ Cache laporan dan statistik yang jarang berubah
  8. ✅ Jalankan OPTIMIZE TABLE pada tabel yang sering berubah

Kesimpulan

Optimasi database MySQL bukan pekerjaan sekali jadi, melainkan kebiasaan: diagnosa dengan EXPLAIN dan slow query log, perkuat dengan index yang tepat, tulis query yang hemat, dan rawat tabel secara berkala. Untuk pemilik source code siap pakai, langkah-langkah di atas adalah cara termurah menaikkan performa — jauh lebih murah daripada langsung upgrade server.

Sedang mencari source code aplikasi web yang fondasi databasenya sudah rapi? Jelajahi katalog produk Kios Koding — dan kalau masih ragu memilih, baca dulu panduan memilih source code yang tepat agar tidak salah beli.