web-dev-qa-db-ja.com

MongoDB範囲のページネーション

多くのレコードを含むMongoDBコレクションのページネーションにskip()を使用するのは遅く、推奨されていないと言われています。

範囲付きページネーション(> _id比較に基づく)を使用できます

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

前を表示するのに適しています。 &次へボタン-しかし、実際のページ番号1 ... 5 6 7 ... 124を表示したい場合、実装は非常に簡単ではありません-各ページがどの "_id"から始まるかを事前に計算する必要があります。

そこで、2つの質問があります。

1)いつそれについて心配し始めるべきですか? skip()の顕著な減速を伴う「レコードが多すぎる」場合1000? 1000000?

2)範囲付きページネーションを使用する場合、実際のページ番号とリンクを表示するための最良のアプローチは何ですか?

66
Roman

良い質問!

「多すぎますか?」 -もちろん、それはデータサイズとパフォーマンスの要件に依存します。個人的には、500〜1000を超えるレコードをスキップすると不快に感じます。

実際の答えは要件によって異なります。ここに、現代のサイトが行うこと(または、少なくともそれらのいくつか)があります。

まず、navbarは次のようになります。

1 2 3 ... 457

合計レコード数とページサイズから最終ページ番号を取得します。 3ページにジャンプしましょう。最初のレコードからいくつかスキップする必要があります。結果が到着すると、3ページ目の最初のレコードのIDがわかります。

1 2 3 4 5 ... 457

さらにスキップして5ページに進みましょう。

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

あなたはアイデアを得る。各ポイントで、最初、最後、および現在のページと、現在のページから前後に2つのページが表示されます。

問い合わせ

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});
97

一般的な答えを出すのは困難です。表示される結果のセットを作成するために使用しているクエリ(複数可)に大きく依存するためです。インデックスのみを使用して結果を見つけることができ、インデックス順に表示される場合、db.dataset.find()。limit()。skip()は、多数のスキップがあっても良好に実行できます。これはおそらく、コーディングするための最も簡単なアプローチです。ただし、その場合でも、ページ番号をキャッシュしてインデックス値に結び付けることができれば、たとえば、ページ71を表示したい2番目と3番目の人のためにより速くすることができます。

他の誰かがデータをページングしている間にドキュメントが追加および削除される非常に動的なデータセットでは、そのようなキャッシュはすぐに古くなってしまい、制限とスキップの方法だけが良い結果を与えるのに十分な信頼性があります。

6
Tad Marshall

「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が「Carlos」の場合、firstNameが「Carlos」と等しくない場合は、このidよりも大きい必要があり、「Carlos」よりも大きい必要があります

唯一の問題は、特定のページ番号にナビゲートできないことです(おそらくいくつかのコード操作で行うことができます)が、それ以外の多くのメモリと処理を消費するスキップを使用せずに非一意フィールドのページネーションの問題を解決しましたクエリ対象のデータセットの最後に到達するときのパワー。

1
Carlos Ruiz