Mungo Limit / Offset und Count Abfrage

84

Etwas seltsam in Bezug auf die Abfrageleistung ... Ich muss eine Abfrage ausführen, die eine Gesamtanzahl von Dokumenten ausführt und auch eine Ergebnismenge zurückgeben kann, die begrenzt und versetzt werden kann.

Ich habe also insgesamt 57 Dokumente und der Benutzer möchte 10 Dokumente, die um 20 versetzt sind.

Ich kann mir zwei Möglichkeiten vorstellen, dies zu tun: Zuerst werden alle 57 Dokumente abgefragt (als Array zurückgegeben) und dann mit array.slice die gewünschten Dokumente zurückgegeben. Die zweite Option besteht darin, zwei Abfragen auszuführen, die erste mit der nativen 'count'-Methode von mongo, und dann eine zweite Abfrage mit den nativen Aggregatoren $ limit und $ skip von mongo auszuführen.

Was denkst du würde besser skalieren? Alles in einer Abfrage erledigen oder zwei separate ausführen?

Bearbeiten:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});
leepowell
quelle
Ich bin mir bei Mongoose count()nicht sicher, aber die Standardfunktion in PHP berücksichtigt limitoder skipberücksichtigt sie nur, wenn Sie dazu aufgefordert werden. Wenn Sie nur eine Abfrage von Limit ausführen und überspringen und dann die Zählung abrufen, sollte dies hier wahrscheinlich die leistungsstärkste Lösung sein. Wie können Sie jedoch feststellen, dass es 57 Dokumente gibt, wenn Sie nicht zwei Abfragen durchführen, um zu zählen, was aktuell vorhanden ist? Haben Sie eine statische Nummer, die sich nie ändert? Wenn nicht, müssen Sie sowohl überspringen als auch begrenzen und dann zählen.
Sammaye
Entschuldigung, ich habe über db.collection.find(<query>).count();
Mongos
Entschuldigung, ich war es, ich habe Ihre Frage falsch verstanden. Hmmm, eigentlich bin ich mir nicht sicher, was besser wäre. Wird Ihre Ergebnismenge immer sehr niedrig sein wie bei 57 Dokumenten? In diesem Fall ist das clientseitige Slice möglicherweise eine Millisekunde leistungsfähiger.
Sammaye
Ich habe der ursprünglichen Frage ein Beispiel hinzugefügt. Ich glaube nicht, dass die Daten jemals mehr als 10.000 erreichen werden, aber möglicherweise könnte dies der Fall sein.
Leepowell
Bei 10.000 Datensätzen konnte festgestellt werden, dass die Speicherbehandlung von JS weniger leistungsfähig ist als die count()Funktion von MongoDB. Die count()Funktion in MongoDB ist relativ langsam, aber immer noch so schnell wie die meisten clientseitigen Variationen bei größeren Sets und möglicherweise schneller als die clientseitige Zählung hier. Dieser Teil ist jedoch subjektiv für Ihre eigenen Tests. Wohlgemerkt, ich habe schon früher Arrays mit einer Länge von 10.000 gezählt, so dass es auf der Clientseite möglicherweise schneller ist. Bei 10.000 Elementen ist es sehr schwer zu sagen.
Sammaye

Antworten:

129

Ich schlage vor, 2 Abfragen zu verwenden:

  1. db.collection.count()gibt die Gesamtzahl der Artikel zurück. Dieser Wert wird irgendwo in Mongo gespeichert und nicht berechnet.

  2. db.collection.find().skip(20).limit(10)Hier gehe ich davon aus, dass Sie eine Sortierung nach einem Feld verwenden können. Vergessen Sie also nicht, einen Index für dieses Feld hinzuzufügen. Diese Abfrage wird auch schnell sein.

Ich denke, dass Sie nicht alle Elemente abfragen und dann überspringen und nehmen sollten, da Sie später, wenn Sie über große Datenmengen verfügen, Probleme mit der Datenübertragung und -verarbeitung haben werden.

user854301
quelle
1
Was ich schreibe, ist nur ein Kommentar ohne Vorwand, aber ich habe gehört, dass die .skip()Anweisung für die CPU schwer ist, weil sie an den Anfang der Sammlung geht und den im Parameter von angegebenen Wert erreicht .skip(). Es kann einen echten Einfluss auf die große Sammlung haben! Aber ich weiß .skip()sowieso nicht, welches zwischen den Einsätzen am schwersten ist, oder ich bekomme die gesamte Kollektion und schneide mit JS ... Was denkst du?
Zachary Dahan
2
@Stuffix Ich habe die gleichen Bedenken bezüglich der Verwendung gehört .skip(). Diese Antwort korrigiert es und empfiehlt, einen Filter für ein Datumsfeld zu verwenden. Man könnte dies mit den .skip()& .take()Methoden verwenden. Dies scheint eine gute Idee zu sein. Ich habe jedoch Probleme mit der Frage dieses OP, wie die Gesamtzahl der Dokumente ermittelt werden kann. .skip()Wie können wir eine genaue Zählung durchführen, wenn ein Filter verwendet wird, um die Auswirkungen auf die Leistung zu bekämpfen ? Die in der Datenbank gespeicherte Anzahl spiegelt nicht unseren gefilterten Datensatz wider.
Michael Leanos
Hallo @MichaelLeanos, ich habe das gleiche Problem: dh wie man die Gesamtzahl der Dokumente ermittelt. Wenn ein Filter verwendet wird, wie können wir dann eine genaue Zählung haben? Hast du die Lösung dafür bekommen?
Virsha
@virsha, verwenden Sie diese Option, um cursor.count()die Anzahl der gefilterten Dokumente zurückzugeben (es wird keine Abfrage ausgeführt, sondern die Anzahl der übereinstimmenden Dokumente). Stellen Sie sicher, dass Ihre Filter- und Auftragseigenschaften indiziert sind und alles in Ordnung ist.
user854301
@virsha Die Verwendung von cursor.count()sollte funktionieren, wie @ user854301 hervorhob. Am Ende fügte ich meiner API ( /api/my-colllection/stats) einen Endpunkt hinzu , mit dem ich mithilfe der Funktion db.collection.stats von Mongoose verschiedene Statistiken für meine Sammlungen zurückgab . Da ich dies wirklich nur für mein Front-End benötigte, habe ich nur den Endpunkt abgefragt, um diese Informationen unabhängig von meiner serverseitigen Paginierung zurückzugeben.
Michael Leanos
19

Anstatt zwei separate Abfragen zu verwenden, können Sie diese aggregate()in einer einzigen Abfrage verwenden:

Aggregierte "$ facet" können schneller abgerufen werden, die Gesamtzahl und die Daten mit Skip & Limit

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

Ausgabe wie folgt: -

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

Besuchen Sie auch https://docs.mongodb.com/manual/reference/operator/aggregation/facet/

DhineshJa
quelle
2

Nachdem ich dieses Problem selbst angehen musste, möchte ich auf der Antwort von user854301 aufbauen.

Mungo ^ 4.13.8 Ich konnte eine aufgerufene Funktion verwenden, mit toConstructor()der ich vermeiden konnte, die Abfrage mehrmals zu erstellen, wenn Filter angewendet werden. Ich weiß, dass diese Funktion auch in älteren Versionen verfügbar ist, aber Sie müssen die Mongoose-Dokumente überprüfen, um dies zu bestätigen.

Das Folgende verwendet Bluebird-Versprechen:

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

Jetzt gibt die Zählabfrage die Gesamtzahl der übereinstimmenden Datensätze zurück und die zurückgegebenen Daten sind eine Teilmenge der Gesamtzahl der Datensätze.

Bitte beachten Sie das () um query (), das die Abfrage erstellt.

oli_taz
quelle
0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

Anstatt zwei Abfragen zu verwenden, um die Gesamtzahl zu ermitteln und den übereinstimmenden Datensatz zu überspringen.
$ facet ist der beste und optimierte Weg.

  1. Passen Sie den Datensatz an
  2. Finde total_count
  3. Überspringen Sie die Aufzeichnung
  4. Und kann auch Daten entsprechend unseren Anforderungen in der Abfrage umformen.
SANJEEV RAVI
quelle
1
Bitte fügen Sie Ihrer Antwort eine Erklärung hinzu, damit andere daraus lernen können
Nico Haase