Ответ
Пагинация — это техника разделения большого результата SQL-запроса на дискретные страницы для постраничного вывода в UI (например, списка товаров, записей в логе).
Классический подход с LIMIT/OFFSET (MySQL/PostgreSQL):
// Параметры пагинации
$perPage = 20;
$currentPage = max(1, (int) ($_GET['page'] ?? 1));
$offset = ($currentPage - 1) * $perPage;
// Запрос с пагинацией. ВАЖНО: Используйте подготовленные выражения!
$stmt = $pdo->prepare("SELECT id, title, created_at FROM articles ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$articles = $stmt->fetchAll();
// Подсчет общего количества страниц
$totalStmt = $pdo->query("SELECT COUNT(*) FROM articles");
$totalItems = $totalStmt->fetchColumn();
$totalPages = ceil($totalItems / $perPage);
Проблемы и улучшения:
OFFSETна больших страницах медленный, так как БД должна «пропустить» N записей. Решение — keyset pagination (курсорная).- Keyset Pagination (быстрее для бесконечной прокрутки):
-- Вместо OFFSET 10000, LIMIT 20 -- Используем условие по последнему полученному значению SELECT * FROM articles WHERE created_at < '2023-10-01 00:00:00' -- Значение с предыдущей страницы ORDER BY created_at DESC LIMIT 20; - Пагинация должна быть частью API-ответа:
{ "data": [...], "meta": { "current_page": 2, "total_pages": 50 } }.
Ответ 18+ 🔞
Э, слушай, пагинация — это когда у тебя в базе данных лежит овердохуища записей, а показывать надо по двадцатке, как товары в интернет-магазине. Чисто технически — разбиваем огромную кучу на аккуратные странички.
Старый добрый способ через LIMIT/OFFSET (работает везде):
Смотри, суть простая: говорим базе — «дай мне 20 записей, начиная с сороковой». Всё, блядь, элементарно.
// Сколько показывать на странице и какую страницу хочет юзер
$perPage = 20;
$currentPage = max(1, (int) ($_GET['page'] ?? 1)); // Чтобы не было нуля или хуйни
$offset = ($currentPage - 1) * $perPage; // Вот эта формула — священный грааль
// Сам запрос. СУКА, НЕ ЗАБУДЬ ПРО ПОДГОТОВКУ, А ТО SQL-ИНЪЕКЦИЮ ЛОВИТЬ БУДЕШЬ!
$stmt = $pdo->prepare("SELECT id, title, created_at FROM articles ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$articles = $stmt->fetchAll();
// А теперь надо посчитать, сколько всего страниц, чтобы кнопочки «вперёд» не было на пустом месте
$totalStmt = $pdo->query("SELECT COUNT(*) FROM articles");
$totalItems = $totalStmt->fetchColumn();
$totalPages = ceil($totalItems / $perPage); // Округляем вверх, ёпта
Но тут есть подводный камень, чувак:
Представь, ты на 500-й странице. Запрос с OFFSET 10000 — это пиздец какой медленный, потому что базе надо в уме отсчитать и отбросить эти десять тысяч строк, прежде чем выдать тебе 20. Это как искать иголку в стоге сена, перебирая каждую соломинку. Доверия к такому подходу на больших данных — ноль, ебать.
Поэтому умные дяди придумали Keyset Pagination (или курсорную): Суть в чём? Мы не просим «пропусти 10000», а говорим: «дай 20 записей, которые были созданы раньше вот этой конкретной даты с прошлой страницы». Это как закладка в книге, блядь.
-- Вместо OFFSET 10000, LIMIT 20
-- Берём значение created_at с последней записи на предыдущей странице и используем как фильтр
SELECT * FROM articles
WHERE created_at < '2023-10-01 00:00:00' -- Вот эта самая закладка
ORDER BY created_at DESC
LIMIT 20;
Скорость — просто ни хуя себе, потому что база может использовать индекс по created_at и сразу прыгнуть в нужное место. Идеально для бесконечной ленты или API.
И последнее, золотое правило: когда отдаёшь данные на фронт или в API, хуй с горы просто массив записей. Отдавай нормальный ответ с метаданными:
{
"data": [...],
"meta": {
"current_page": 2,
"total_pages": 50,
"per_page": 20
}
}
Чтобы фронтендер не гадал, на какой он странице и есть ли ещё. Всё чётко, по-взрослому.