Что такое пагинация в контексте работы с базами данных?

Ответ

Пагинация — это техника разделения большого результата 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
  }
}

Чтобы фронтендер не гадал, на какой он странице и есть ли ещё. Всё чётко, по-взрослому.