Pagination adalah cara membagi data menjadi beberapa halaman agar pengguna tidak perlu memuat seluruh isi tabel sekaligus. Di aplikasi kecil, hampir semua pendekatan terlihat baik-baik saja. Masalah biasanya baru muncul ketika data bertambah: halaman terakhir terasa lambat, urutan data berubah saat ada transaksi baru, atau database harus melewati ribuan baris hanya untuk menampilkan sepuluh baris berikutnya.
Untuk aplikasi PHP dan MySQL, pagination bukan sekadar menambahkan LIMIT. Kita juga perlu menentukan urutan data yang konsisten, memvalidasi input halaman, memilih indeks yang tepat, dan memahami kapan metode klasik berbasis offset mulai kurang efisien.
Masalah pada pagination berbasis offset
Pola yang paling umum terlihat seperti ini:
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
LIMIT 20 OFFSET 1000;Artinya, MySQL diminta melewati 1.000 baris terlebih dahulu, lalu mengembalikan 20 baris berikutnya. Untuk halaman awal, pendekatan ini sederhana dan mudah dipahami. Namun, semakin besar nilai OFFSET, semakin banyak baris yang harus diproses sebelum hasil yang diminta ditemukan.
MySQL memang menyediakan klausa LIMIT untuk membatasi jumlah baris hasil query, tetapi itu tidak otomatis berarti database hanya bekerja sebanyak jumlah baris yang dikirim ke browser. Beban sebenarnya bergantung pada kondisi pencarian, pengurutan, dan indeks yang tersedia.
Pastikan urutan data benar-benar stabil
Kesalahan yang sering terjadi adalah mengurutkan data hanya berdasarkan kolom yang bisa memiliki nilai sama, misalnya created_at. Jika beberapa artikel dibuat pada waktu yang sama, database bebas menentukan urutan di antara baris-baris tersebut. Akibatnya, pengguna bisa melihat item yang sama di dua halaman atau melewatkan item tertentu.
Tambahkan kolom unik sebagai pengurutan kedua:
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 0;Dengan begitu, created_at menentukan urutan utama, sedangkan id menjadi penentu jika waktunya sama. Pola ini juga membantu ketika kita nanti berpindah ke keyset pagination.
Validasi nomor halaman di PHP
Nomor halaman berasal dari pengguna, biasanya melalui query string seperti ?page=3. Jangan langsung memasukkannya ke query. Ubah ke bilangan bulat, tetapkan nilai minimum, dan batasi ukuran halaman agar tidak ada permintaan yang mencoba mengambil terlalu banyak data.
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT);
$page = $page && $page > 0 ? $page : 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;Nilai $perPage sebaiknya dikendalikan oleh aplikasi, bukan bebas ditentukan pengguna. Jika memang ingin memberi pilihan 20, 50, atau 100 item per halaman, gunakan daftar nilai yang diizinkan.
Untuk query dengan PDO, gunakan prepared statement untuk parameter yang memang dapat dibind. Nilai seperti LIMIT dan OFFSET kadang memerlukan penanganan tipe data khusus tergantung driver, sehingga pastikan nilainya sudah berupa integer tervalidasi sebelum dipakai.
$sql = 'SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC, id DESC
LIMIT :limit OFFSET :offset';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);Kapan keyset pagination lebih masuk akal?
Keyset pagination, yang juga sering disebut cursor pagination, tidak menghitung berapa banyak baris yang harus dilewati. Sebagai gantinya, aplikasi mengirimkan penanda dari item terakhir yang sudah dilihat.
Misalnya, halaman pertama berakhir pada artikel dengan created_at tertentu dan id 120. Query untuk halaman berikutnya dapat mencari data yang berada setelah titik tersebut:
SELECT id, title, created_at
FROM articles
WHERE (created_at < :lastCreatedAt)
OR (created_at = :lastCreatedAt AND id < :lastId)
ORDER BY created_at DESC, id DESC
LIMIT :limit;Pendekatan ini biasanya lebih cocok untuk feed, log aktivitas, daftar pesanan, atau API yang datanya terus bertambah. Aplikasi tidak perlu melompat melewati ribuan baris untuk membuka bagian yang jauh di bawah.
Kekurangannya, pengguna tidak bisa langsung melompat ke halaman nomor 50 dengan mudah. Karena itu, keyset bukan pengganti mutlak offset. Gunakan offset jika navigasi berbasis nomor halaman penting dan jumlah datanya masih terkendali. Gunakan keyset jika daftar dibaca berurutan dan performa pada data besar lebih penting.
Indeks harus mengikuti pola query
Query pagination sering menggabungkan filter dan pengurutan. Contohnya:
SELECT id, title, created_at
FROM articles
WHERE status = 'published'
ORDER BY created_at DESC, id DESC
LIMIT 20;Jika query seperti ini sering dipakai, indeks yang relevan dapat membantu database menemukan data yang dibutuhkan dengan lebih efisien. Namun, jangan menambah indeks secara membabi buta. Setiap indeks mempercepat sebagian pembacaan, tetapi juga menambah pekerjaan ketika data ditulis atau diperbarui.
Gunakan EXPLAIN untuk melihat bagaimana MySQL menjalankan query. Periksa apakah indeks digunakan, berapa banyak baris yang diperkirakan dibaca, dan apakah database harus melakukan pengurutan tambahan. Uji dengan data yang mendekati kondisi produksi, bukan hanya dengan 30 baris di komputer lokal.
Jangan selalu menghitung total halaman
Banyak antarmuka membutuhkan jumlah halaman, sehingga aplikasi menjalankan query tambahan:
SELECT COUNT(*) FROM articles WHERE status = 'published';Untuk tabel kecil, ini tidak menjadi masalah. Namun, pada tabel besar atau filter yang kompleks, penghitungan total dapat menjadi beban yang tidak perlu, terutama jika pengguna sebenarnya hanya membutuhkan tombol “Muat lebih banyak”.
Alternatif sederhana adalah mengambil satu baris ekstra. Jika ukuran halaman 20, ambil 21 baris. Dua puluh baris pertama ditampilkan, sedangkan baris ke-21 hanya dipakai untuk menentukan apakah masih ada data berikutnya.
Yang bisa dilakukan sekarang
- Tambahkan pengurutan kedua menggunakan kolom unik seperti
id. - Validasi nomor halaman dan batasi ukuran halaman di sisi server.
- Gunakan prepared statement untuk nilai yang berasal dari pengguna.
- Periksa query pagination dengan
EXPLAIN. - Pastikan kolom filter dan pengurutan memiliki strategi indeks yang masuk akal.
- Pertimbangkan keyset pagination untuk feed atau daftar yang sangat besar.
- Hindari menghitung total halaman jika fitur tersebut tidak benar-benar dibutuhkan.
Pagination yang baik bukan hanya soal tampilan. Ia adalah keputusan kecil di antara antarmuka, kode PHP, dan cara database membaca data. Dengan urutan yang stabil, input yang aman, serta metode yang sesuai ukuran data, daftar sederhana dapat tetap cepat tanpa harus menunggu sampai server mulai kewalahan.
Sumber & bacaan lebih lanjut
- MySQL 8.4 Reference Manual — SELECT Statement
- PHP Manual — PDO::prepare
- PHP Manual — Prepared Statements and Stored Procedures
– Rio Yotto @rioyotto
