sql >> Database >  >> NoSQL >> MongoDB

Efficiënt pagineren in MongoDB met mgo

Helaas is de mgo.v2 driver biedt geen API-aanroepen om cursor.min() . te specificeren .

Maar er is een oplossing. De mgo.Database type biedt een Database.Run() methode om MongoDB-opdrachten uit te voeren. De beschikbare commando's en hun documentatie zijn hier te vinden:Database commando's

Beginnend met MongoDB 3.2, een nieuwe find commando beschikbaar is dat kan worden gebruikt om query's uit te voeren, en het ondersteunt het specificeren van de min argument dat het eerste indexitem aangeeft waarvan de resultaten worden weergegeven.

Goed. Wat we moeten doen is na elke batch (documenten van een pagina) de min . genereren document van het laatste document van het zoekresultaat, dat de waarden moet bevatten van het indexitem dat werd gebruikt om de zoekopdracht uit te voeren, en dan kan de volgende batch (de documenten van de volgende pagina) worden verkregen door dit min indexitem eerder in te stellen om de zoekopdracht uit te voeren.

Dit indexitem – laten we het cursor noemen vanaf nu– kan worden gecodeerd tot een string en samen met de resultaten naar de klant gestuurd, en wanneer de klant de volgende pagina wil, stuurt hij de cursor terug zegt dat hij resultaten wil beginnen na deze cursor.

Handmatig doen (de "moeilijke" manier)

Het uit te voeren commando kan verschillende vormen hebben, maar de naam van het commando (find ) moet de eerste zijn in het gemarshalde resultaat, dus gebruiken we bson.D (die de orde behoudt in tegenstelling tot bson.M ):

limit := 10
cmd := bson.D{
    {Name: "find", Value: "users"},
    {Name: "filter", Value: bson.M{"country": "USA"}},
    {Name: "sort", Value: []bson.D{
        {Name: "name", Value: 1},
        {Name: "_id", Value: 1},
    },
    {Name: "limit", Value: limit},
    {Name: "batchSize", Value: limit},
    {Name: "singleBatch", Value: true},
}
if min != nil {
    // min is inclusive, must skip first (which is the previous last)
    cmd = append(cmd,
        bson.DocElem{Name: "skip", Value: 1},
        bson.DocElem{Name: "min", Value: min},
    )
}

Het resultaat van het uitvoeren van een MongoDB find commando met Database.Run() kan worden vastgelegd met het volgende type:

var res struct {
    OK       int `bson:"ok"`
    WaitedMS int `bson:"waitedMS"`
    Cursor   struct {
        ID         interface{} `bson:"id"`
        NS         string      `bson:"ns"`
        FirstBatch []bson.Raw  `bson:"firstBatch"`
    } `bson:"cursor"`
}

db := session.DB("")
if err := db.Run(cmd, &res); err != nil {
    // Handle error (abort)
}

We hebben nu de resultaten, maar in een segment van het type []bson.Raw . Maar we willen het in een segment van het type []*User . Dit is waar Collection.NewIter() komt van pas. Het kan een waarde van het type []bson.Raw transformeren (unmarshaleren) in elk type dat we gewoonlijk doorgeven aan Query.All() of Iter.All() . Goed. Laten we eens kijken:

firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)

We hebben nu de gebruikers van de volgende pagina. Er rest nog maar één ding:de cursor genereren die moet worden gebruikt om de volgende pagina te krijgen als we die ooit nodig hebben:

if len(users) > 0 {
    lastUser := users[len(users)-1]
    cursorData := []bson.D{
        {Name: "country", Value: lastUser.Country},
        {Name: "name", Value: lastUser.Name},
        {Name: "_id", Value: lastUser.ID},
    }
} else {
    // No more users found, use the last cursor
}

Dit is allemaal goed, maar hoe zetten we een cursorData om? naar string en vice versa? We kunnen bson.Marshal() . gebruiken en bson.Unmarshal() gecombineerd met base64-codering; het gebruik van base64.RawURLEncoding geeft ons een webveilige cursorreeks, een die kan worden toegevoegd aan URL-query's zonder te ontsnappen.

Hier is een voorbeeldimplementatie:

// CreateCursor returns a web-safe cursor string from the specified fields.
// The returned cursor string is safe to include in URL queries without escaping.
func CreateCursor(cursorData bson.D) (string, error) {
    // bson.Marshal() never returns error, so I skip a check and early return
    // (but I do return the error if it would ever happen)
    data, err := bson.Marshal(cursorData)
    return base64.RawURLEncoding.EncodeToString(data), err
}

// ParseCursor parses the cursor string and returns the cursor data.
func ParseCursor(c string) (cursorData bson.D, err error) {
    var data []byte
    if data, err = base64.RawURLEncoding.DecodeString(c); err != nil {
        return
    }

    err = bson.Unmarshal(data, &cursorData)
    return
}

En we hebben eindelijk onze efficiënte, maar niet zo korte MongoDB mgo paging-functionaliteit. Lees verder...

Gebruik github.com/icza/minquery (de "gemakkelijke" manier)

De handmatige manier is vrij lang; het kan algemeen worden gemaakt en geautomatiseerd . Dit is waar github.com/icza/minquery komt in beeld (openbaarmaking:ik ben de auteur ). Het biedt een wrapper om een ​​MongoDB find te configureren en uit te voeren commando, waarmee u een cursor kunt specificeren, en na het uitvoeren van de query, geeft het u de nieuwe cursor terug die moet worden gebruikt om de volgende reeks resultaten op te vragen. De wrapper is de MinQuery type dat erg lijkt op mgo.Query maar het ondersteunt het specificeren van MongoDB's min via de MinQuery.Cursor() methode.

De bovenstaande oplossing met behulp van minquery ziet er zo uit:

q := minquery.New(session.DB(""), "users", bson.M{"country" : "USA"}).
    Sort("name", "_id").Limit(10)
// If this is not the first page, set cursor:
// getLastCursor() represents your logic how you acquire the last cursor.
if cursor := getLastCursor(); cursor != "" {
    q = q.Cursor(cursor)
}

var users []*User
newCursor, err := q.All(&users, "country", "name", "_id")

En dat is alles. newCursor is de cursor die moet worden gebruikt om de volgende batch op te halen.

Opmerking #1: Bij het aanroepen van MinQuery.All() , moet u de namen van de cursorvelden opgeven, dit zal worden gebruikt om de cursorgegevens (en uiteindelijk de cursorreeks) op te bouwen.

Opmerking #2: Als u gedeeltelijke resultaten ophaalt (met behulp van MinQuery.Select() ), moet u alle velden opnemen die deel uitmaken van de cursor (het indexitem), zelfs als u niet van plan bent ze rechtstreeks te gebruiken, anders MinQuery.All() heeft niet alle waarden van de cursorvelden en kan dus niet de juiste cursorwaarde maken.

Bekijk het pakketdocument van minquery hier:https://godoc.org/github.com/icza/minquery, het is nogal kort en hopelijk schoon.




  1. Mongo:vind items die geen bepaald veld hebben

  2. Node Js:Redis-taak wordt niet voltooid nadat de taak is voltooid

  3. C# mongo-query's met json-tekenreeksen

  4. Redis en opvragen van waarden