Медленное разбиение на страницы в тоннах записей в mongodb - PullRequest
51 голосов
/ 29 августа 2011

У меня более 300 тысяч записей в одной коллекции в Монго.

Когда я запускаю этот очень простой запрос:

db.myCollection.find().limit(5);

Это займет всего несколько миллисекунд.

Но когда я использую пропустить в запросе:

db.myCollection.find().skip(200000).limit(5)

Он ничего не возвращает ... он работает в течение нескольких минут и ничего не возвращает.

Как сделать лучше?

Ответы [ 3 ]

88 голосов
/ 29 августа 2011

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

Так что, если вы начнете с

db.myCollection.find().limit(100).sort({created_date:true});

, а затем извлечете дату создания last документ возвращается курсором в переменную max_created_date_from_last_result, вы можете получить следующую страницу с гораздо более эффективным (при условии, что у вас есть индекс по created_date) запросом

db.myCollection.find({created_date : { $gt : max_created_date_from_last_result } }).limit(100).sort({created_date:true}); 
66 голосов
/ 29 августа 2011

Из документации MongoDB :

Расходы на пейджинг

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

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

Вы должны задать себе вопрос: как часто вам нужна 40000-я страница? Также см. эту статью;

2 голосов
/ 04 сентября 2018

Мне показалось целесообразным объединить две концепции вместе (и пропуск + предел, и поиск + лимит). Проблема с skip + limit - низкая производительность, когда у вас много документов (особенно больших). Проблема с find + limit - вы не можете перейти на произвольную страницу. Я хочу быть в состоянии разбивать на страницы, не делая это последовательно.

Шаги, которые я делаю:

  1. Создайте индекс, основанный на том, как вы хотите отсортировать свои документы, или просто используйте индекс _id по умолчанию (именно это я и использовал)
  2. Знайте начальное значение, размер страницы и страницу, на которую вы хотите перейти
  3. Проект + пропустить + ограничить значение, которое вы должны начать с
  4. Найти + ограничить результаты страницы

Это выглядит примерно так, если я хочу получить страницу 5432 из 16 записей (в javascript):

let page = 5432;
let page_size = 16;
let skip_size = page * page_size;

let retval = await db.collection(...).find().sort({ "_id": 1 }).project({ "_id": 1 }).skip(skip_size).limit(1).toArray();
let start_id = retval[0].id;

retval = await db.collection(...).find({ "_id": { "$gte": new mongo.ObjectID(start_id) } }).sort({ "_id": 1 }).project(...).limit(page_size).toArray();

Это работает, потому что пропуск прогнозируемого индекса выполняется очень быстро, даже если вы пропускаете миллионы записей (что я и делаю). если вы запустите explain("executionStats"), он все равно будет иметь большое число для totalDocsExamined, но из-за проекции на индекс он очень быстрый (по сути, большие двоичные объекты данных никогда не проверяются). Затем со значением начала страницы вы можете очень быстро получить следующую страницу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...