MongoDB ранжированная нумерация страниц - PullRequest
65 голосов
/ 14 марта 2012

Говорят, что использование skip () для разбивки на страницы в коллекции MongoDB со многими записями является медленным и не рекомендуется.

Можно использовать ранжирование на основе диапазона (на основе сравнения> _id)

db.items.find({_id: {$gt: ObjectId('4f4a3ba2751e88780b000000')}});

Это хорошо для отображения пред.Кнопки & и далее - но это не очень легко реализовать, когда вы хотите отобразить фактические номера страниц 1 ... 5 6 7 ... 124 - вам нужно предварительно рассчитать, с какого «_id» начинается каждая страница.

Итак, у меня два вопроса:

1) Когда я должен начать беспокоиться об этом?Когда «слишком много записей» с заметным замедлением для skip ()?1 000?1 000 000?

2) Как лучше всего отображать ссылки с реальными номерами страниц при использовании ранжирования на страницы?

Ответы [ 3 ]

96 голосов
/ 14 марта 2012

Хороший вопрос!

"Сколько это слишком много?"- это, конечно, зависит от размера ваших данных и требований к производительности.Лично мне неловко, когда я пропускаю более 500-1000 записей.

Фактический ответ зависит от ваших требований.Вот что делают современные сайты (или, по крайней мере, некоторые из них).

Во-первых, навигационная панель выглядит так:

1 2 3 ... 457

Они получают окончательный номер страницы из общего количества записей и размера страницы,Давайте перейдем к странице 3. Это будет связано с некоторым пропуском из первой записи.Когда результаты будут получены, вы узнаете идентификатор первой записи на странице 3.

1 2 3 4 5 ... 457

Давайте пропустим еще немного и перейдем к странице 5.

1 ... 3 4 5 6 7 ... 457

Вы поняли идею.В каждой точке вы видите первую, последнюю и текущую страницы, а также две страницы вперед и назад от текущей страницы.

Запросы

var current_id; // id of first record on current page.

// go to page current+N
db.collection.find({_id: {$gte: current_id}}).
              skip(N * page_size).
              limit(page_size).
              sort({_id: 1});

// go to page current-N
// note that due to the nature of skipping back,
// this query will get you records in reverse order 
// (last records on the page being first in the resultset)
// You should reverse them in the app.
db.collection.find({_id: {$lt: current_id}}).
              skip((N-1)*page_size).
              limit(page_size).
              sort({_id: -1});
6 голосов
/ 14 марта 2012

Сложно дать общий ответ, потому что это во многом зависит от того, какой запрос (или запросы) вы используете для построения набора отображаемых результатов. Если результаты могут быть найдены с использованием только индекса и представлены в порядке индекса, тогда db.dataset.find (). Limit (). Skip () может работать хорошо даже при большом количестве пропусков. Это, вероятно, самый простой подход к кодированию. Но даже в этом случае, если вы можете кэшировать номера страниц и привязывать их к значениям индекса, вы можете сделать это быстрее для второго и третьего лица, которое хочет просмотреть страницу 71, например.

В очень динамичном наборе данных, в котором документы будут добавляться и удаляться, пока кто-то другой просматривает данные, такое кэширование быстро устареет, и метод ограничения и пропуска может быть единственным, достаточно надежным для получения хороших результатов. .

1 голос
/ 18 февраля 2019

Недавно я столкнулся с той же проблемой при попытке разбить запрос на страницы при использовании поля, которое не было уникальным, например «FirstName». Идея этого запроса состоит в том, чтобы иметь возможность реализовать разбиение на страницы в неуникальном поле без использования skip ()

Основной проблемой здесь является возможность запроса поля, которое не является уникальным "FirstName", потому что произойдет следующее:

  1. $ gt: {"FirstName": "Carlos"} -> это пропустит все записи, где имя "Carlos"
  2. $ gte: {"FirstName": "Carlos"} -> всегда будет возвращать один и тот же набор данных

Поэтому решение, которое я нашел, заключалось в том, чтобы сделать часть запроса $ match уникальной, объединив поле целевого поиска со вторичным полем, чтобы сделать его уникальным поиском.

По возрастанию:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$gt: 'Carlos'}}]}},
    {$sort: {'FirstName': 1, '_id': 1}},
    {$limit: 10}
    ])

По убыванию:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$lt: 'Carlos'}}]}},
    {$sort: {'FirstName': -1, '_id': 1}},
    {$limit: 10}
    ])

Часть $ match этого запроса в основном ведет себя как оператор if: если firstName - "Карлос", то он также должен быть больше, чем этот идентификатор если firstName не равно "Carlos", тогда оно должно быть больше, чем "Carlos"

Единственная проблема заключается в том, что вы не можете перейти к определенному номеру страницы (это, вероятно, можно сделать с помощью некоторых манипуляций с кодом), но кроме этого он решил мою проблему с разбиением на страницы для неуникальных полей без необходимости использовать пропуск, который съедает много памяти и вычислительной мощности при завершении работы с любым набором данных, к которому вы обращаетесь.

...