Я уже давно внес в свой стиль программирования негласное правило о том, что все перечисления, будь это таблицы или списки, картинки или комментарии, должны загружаться порциями и поддерживать постраничную загрузку. Дело в том, что потом добавить этот функционал бывает гораздо сложнее, нежели чем сделать это сразу. Особенно на этапе разработки этим многие пренебрегают. Основным способом сделать постраничную загрузку является имплементация limit-offset подхода. Сейчас мы будем говорить о данных, возвращаемых из базы, соответственно, примеры я тоже буду строить отталкиваясь от этого. Но limit-offset, естественно, применим и для других случаев. Но что будет, если у нас высока вероятность появления новых данных? Возьмем для примера новости. Например, у нас на данный момент 20 новостей, а мы выводим по 10. Вывели первые 10 новостей, но еще до того как мы перешли к следующим десяти новостям, у нас появилась еще одна новость. Каждая новая запись будет смещать новости, и в результатах 2-й страницы мы увидим дубликат последней записи из первой выборки.
Думаю, наглядно будет представить пример на Zend Framework 1.x. У нас есть наипростейшая таблица NEWS с полями: ID, TIME, TEXT.
public function getNews($offset = 0) { $select = $this->select() ->order('time DESC') ->limit(10, $offset); return $this->fetchAll($select); }
Это обычный limit-offset. Как же избежать того случая, когда появляются дубликаты? Я для этого запоминаю какое-нибудь уникальное значение последней записи. Это может быть ID, какой-то хэш, пара значений и т.д. Главное — нам нужно отличить последний объект, и в следующей выборке вернуть N элементов после него. А еще очень важно — это то, что это значение должно быть привязано к сортировке.
Вернемся к нашим новостям. Уникальным значением для каждой новости будет ее ID. Этот ID у нас является первичным ключом и имеет опцию auto_increment. Чем новее новость, тем выше ID. В этом случае можно запоминать ID последней новости из выборки, и вместо offset передавать его в функцию.
public function getNews($lastId = 0) { $select = $this->select() ->order('time DESC') ->limit(10); if ($lastId) { $select->where($this->getAdapter()->quoteInto('id < ?', $lastId)); } return $this->fetchAll($select); }
Конечно, в статье разобран очень простой пример, где ID связан с сортировкой. Но, повторюсь, это не обязательно будет ID, найдите такое уникальное значение, которое соответствует сортировке, и используйте его.
Еще такой подход называют постраничной выборкой по курсору. Используется он не везде и не всегда. Но в комментариях, сообщениях, ленте он может быть очень полезен.