Пользователь листает каталог страница за страницей. На третьей странице видит товар, который уже видел на второй. Запрос вроде бы один и тот же — ORDER BY created_at LIMIT 20 OFFSET 40, но между запросами в таблицу успели вставить новые строки.
OFFSET считает позицию по текущему состоянию таблицы на момент запроса, а не фиксирует снимок на момент первого запроса. Вставка новой строки в начало сортировки сдвигает все позиции после неё на единицу. Пользователь либо видит дубль, либо теряет товар между страницами — зависит от того, куда попала новая строка относительно его текущего OFFSET.
Проблема усиливается под нагрузкой: чем активнее пишут в таблицу, тем чаще страницы "плывут". На статичных справочниках незаметно месяцами, на ленте с частыми вставками — сразу.
String sql = """ SELECT * FROM catalog WHERE created_at < ? ORDER BY created_at DESC LIMIT 20 """;
Чинится keyset pagination: вместо OFFSET передаём значение курсора — created_at последней строки с прошлой страницы. Запрос фильтрует WHERE created_at < ? вместо того чтобы пропускать N строк пересчётом с начала. Позиция больше не зависит от того, что вставили между запросами. Минус — нельзя прыгнуть на произвольную страницу по номеру, только вперёд или назад по курсору. На собеседовании это и проверяют: решение через OFFSET знают почти все, keyset pagination с правильным индексом под сортировку — заметно меньше.
OFFSET читается просто и работает на маленьких таблицах. На таблице, в которую постоянно пишут, он врёт о том, что у пользователя стабильная страница.
Тренажёр: 900+ вопросов и задач, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки