Пагинация PostgreSQL: когда OFFSET заменить курсором

2 мин чтения

Когда курсорная пагинация удобнее OFFSET и какие требования она предъявляет к сортировке и интерфейсу.

Следующая страница по последней записи
  1. ПорядокСортируем по дате и уникальному ID
  2. КурсорПередаём значения последней строки
  3. ВыборкаЧитаем записи после курсора с LIMIT

Первые десять заказов загружаются сразу, а список на тысячной странице заметно тормозит. Большой OFFSET заставляет PostgreSQL обработать и пропущенные строки. Если пользователю достаточно кнопки «Следующие», можно продолжать выборку от последнего показанного ключа. Такой подход называют курсорной, или keyset-пагинацией.

Сначала задаём однозначный порядок

CREATE INDEX events_created_id_idx
ON events(created_at DESC, id DESC);

SELECT id, created_at, title FROM events
ORDER BY created_at DESC, id DESC LIMIT 20;

В примере created_at и id не должны быть NULL. Время само по себе не уникально, поэтому id участвует и в сортировке, и в курсоре. Клиент сохраняет значения последней строки выданной страницы.

Следующая порция по ключу

SELECT id, created_at, title FROM events
WHERE (created_at, id) <
      (TIMESTAMPTZ '2025-10-12 10:00:00+00', 8400)
ORDER BY created_at DESC, id DESC
LIMIT 20;

В реальном приложении граница передаётся параметрами, а не собирается из пользовательской строки. Для фильтра по проекту тот же фильтр обязателен на каждой странице; индекс подбирается под фактическую форму запроса. Курсор можно кодировать для передачи, но само кодирование не даёт прав доступа к строкам.

Что меняется для интерфейса

Курсорный подход удобен для последовательного движения вперёд. Произвольный переход сразу на страницу 127 требует отдельной модели и обычно хуже сочетается с таким API. Появление новых записей перед текущей границей меньше влияет на уже начатый просмотр, однако изменение сортировочных полей существующих записей всё ещё меняет выдачу.

Для небольшого административного списка OFFSET вполне может быть удобнее. Сравните планы и время на той глубине, которой действительно пользуются. Курсор в API и пагинация сайта решают разные задачи: поисковику всё ещё нужны доступные URL и обычные ссылки между страницами.

Источники и дальнейшее чтение