MongoDB-Paginierung

69

Es wird gesagt, dass die Verwendung von skip () für die Paginierung in der MongoDB-Sammlung mit vielen Datensätzen langsam ist und nicht empfohlen wird.

Fernkampf-Paginierung (basierend auf> _id-Vergleich) könnte verwendet werden

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

Es ist gut für die Anzeige von prev. & next-Schaltflächen - aber es ist nicht sehr einfach zu implementieren, wenn Sie die tatsächlichen Seitenzahlen 1 ... 5 6 7 ... 124 anzeigen möchten - Sie müssen vorberechnen, ab welcher "_id" jede Seite beginnt.

Ich habe also zwei Fragen:

1) Wann sollte ich mir darüber Sorgen machen? Wenn es "zu viele Datensätze" mit einer merklichen Verlangsamung für skip () gibt? 1 000? 1 000 000?

2) Was ist der beste Ansatz, um Links mit tatsächlichen Seitenzahlen anzuzeigen, wenn eine Fernkampf-Paginierung verwendet wird?

römisch
quelle

Antworten:

99

Gute Frage!

"Wie viele sind zu viele?" - Das hängt natürlich von Ihrer Datengröße und Ihren Leistungsanforderungen ab. Ich persönlich fühle mich unwohl, wenn ich mehr als 500-1000 Datensätze überspringe.

Die tatsächliche Antwort hängt von Ihren Anforderungen ab. Hier ist, was moderne Websites tun (oder zumindest einige von ihnen).

Erstens sieht die Navigationsleiste folgendermaßen aus:

1 2 3 ... 457

Sie erhalten die endgültige Seitenzahl aus der Gesamtzahl der Datensätze und der Seitengröße. Lassen Sie uns zu Seite 3 springen. Dazu müssen Sie vom ersten Datensatz überspringen. Wenn die Ergebnisse eintreffen, kennen Sie die ID des ersten Datensatzes auf Seite 3.

1 2 3 4 5 ... 457

Lassen Sie uns noch etwas überspringen und zu Seite 5 gehen.

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

Du hast die Idee. An jedem Punkt sehen Sie die erste, letzte und aktuelle Seite sowie zwei Seiten vorwärts und rückwärts von der aktuellen Seite.

Abfragen

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});
Sergio Tulentsev
quelle
Danke, genau das brauche ich. Großartiger kombinierter Ansatz - Bereich von "_id" + skip (), sehr einfach zu bedienen, viel besser als alle Ansätze, die ich heute bei der Suche nach dem Thema gelesen habe.
Roman
1
Gute Antwort, aber bei diesem Ansatz müssen Sie die aktuelle Seitenzahl kennen. Der einzige Weg, es zu wissen - ist es, es auf Anfrage zu senden
Vakuum
1
Funktioniert dies, wenn der Index umgekehrt werden muss? sort ({_ id: -1})
Vakuum
1
und noch eine Frage: Wie bekomme ich effektiv die letzte Seite?
Vakuum
1
Klarstellung - Dies funktioniert nicht, wenn doppelte Werte vorhanden sind. Für den Fall, dass jemand anderes darauf stößt , konnte ich diese Einschränkung überwinden, indem ich mixmax.com/blog/api-paging-built-the-right-way folgte und es an meine eigenen Bedürfnisse anpasste.
Avius
6

Es ist schwierig, eine allgemeine Antwort zu geben, da dies stark davon abhängt, welche Abfrage (oder Abfragen) Sie verwenden, um die angezeigten Ergebnisse zu erstellen. Wenn die Ergebnisse nur anhand des Index gefunden werden können und in der Indexreihenfolge angezeigt werden, kann db.dataset.find (). Limit (). Skip () auch bei einer großen Anzahl von Sprüngen eine gute Leistung erbringen. Dies ist wahrscheinlich der einfachste Ansatz zum Codieren. Aber selbst in diesem Fall können Sie die Seitenzahlen zwischenspeichern und an Indexwerte binden, um sie beispielsweise für die zweite und dritte Person, die beispielsweise Seite 71 anzeigen möchte, zu beschleunigen.

In einem sehr dynamischen Datensatz, in dem Dokumente hinzugefügt und entfernt werden, während eine andere Person Daten durchsucht, ist ein solches Caching schnell veraltet, und die Limit- und Skip-Methode ist möglicherweise die einzige, die zuverlässig genug ist, um gute Ergebnisse zu erzielen.

Tad Marshall
quelle
1

Ich habe kürzlich das gleiche Problem festgestellt, als ich versuchte, eine Anfrage zu paginieren, während ich ein Feld verwendete, das nicht eindeutig war, zum Beispiel "Vorname". Die Idee dieser Abfrage ist es, die Paginierung in einem nicht eindeutigen Feld ohne Verwendung von skip () implementieren zu können.

Das Hauptproblem hierbei ist die Möglichkeit, ein Feld abzufragen, das nicht eindeutig "Vorname" ist, da Folgendes passieren wird:

  1. $ gt: {"Vorname": "Carlos"} -> Dadurch werden alle Datensätze übersprungen, bei denen der Vorname "Carlos" ist.
  2. $ gte: {"Vorname": "Carlos"} -> gibt immer den gleichen Datensatz zurück

Daher bestand die Lösung darin, den $ match-Teil der Abfrage eindeutig zu machen, indem das Zielsuchfeld mit einem sekundären Feld kombiniert wurde, um eine eindeutige Suche zu erstellen.

Aufsteigende Reihenfolge:

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

Absteigende Reihenfolge:

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

Der $ match-Teil dieser Abfrage verhält sich im Grunde genommen wie eine if-Anweisung: Wenn firstName "Carlos" ist, muss er auch größer als diese ID sein. Wenn firstName nicht gleich "Carlos" ist, muss er größer als "Carlos" sein.

Das einzige Problem ist, dass Sie nicht zu einer bestimmten Seitenzahl navigieren können (dies kann wahrscheinlich mit einer gewissen Code-Manipulation durchgeführt werden), aber abgesehen davon hat es mein Problem mit der Paginierung für nicht eindeutige Felder gelöst, ohne überspringen zu müssen, was viel Speicher und Verarbeitung verbraucht Leistung, wenn Sie am Ende des Datensatzes angelangt sind, nach dem Sie fragen.

Carlos Ruiz
quelle