sql >> Databáze >  >> NoSQL >> MongoDB

Efektivní stránkování v MongoDB pomocí mgo

Bohužel mgo.v2 ovladač neposkytuje volání API pro specifikaci cursor.min() .

Ale existuje řešení. mgo.Database type poskytuje Database.Run() metoda pro spouštění jakýchkoli příkazů MongoDB. Dostupné příkazy a jejich dokumentaci naleznete zde:Databázové příkazy

Počínaje MongoDB 3.2, nový find je k dispozici příkaz, který lze použít ke spouštění dotazů, a podporuje zadání min argument, který označuje první položku rejstříku, od které se mají začít vypisovat výsledky.

Dobrý. Co musíme udělat, je po každé dávce (dokumentů na stránce) vygenerovat min dokument z posledního dokumentu výsledku dotazu, který musí obsahovat hodnoty položky rejstříku, která byla použita k provedení dotazu, a poté lze získat další dávku (dokumenty na další stránce) nastavením této minimální položky rejstříku před k provedení dotazu.

Tato položka rejstříku – nazvěme ji kurzor od nynějška – může být zakódováno do string a odeslána klientovi spolu s výsledky, a když klient chce další stránku, pošle zpět kurzor říká, že chce výsledky začínající po tomto kurzoru.

Proveďte to ručně ("tvrdým" způsobem)

Příkaz, který se má provést, může mít různé formy, ale název příkazu (find ) musí být v seřazeném výsledku první, takže použijeme bson.D (který zachovává pořadí na rozdíl od 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},
    )
}

Výsledek spuštění MongoDB find pomocí Database.Run() lze zachytit pomocí následujícího typu:

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

Nyní máme výsledky, ale v řezu typu []bson.Raw . Ale my to chceme v řezu typu []*User . Zde je Collection.NewIter() přijde vhod. Dokáže transformovat (odhlásit) hodnotu typu []bson.Raw do jakéhokoli typu, který obvykle předáme do Query.All() nebo Iter.All() . Dobrý. Podívejme se na to:

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

Nyní máme uživatele další stránky. Zbývá pouze jedna věc:vygenerování kurzoru, který se použije k získání následující stránky, pokud bychom to někdy potřebovali:

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
}

To je všechno dobré, ale jak převedeme cursorData na string a naopak? Můžeme použít bson.Marshal() a bson.Unmarshal() v kombinaci s kódováním base64; použití base64.RawURLEncoding nám poskytne webový kurzorový řetězec, který lze přidat do URL dotazů bez escapování.

Zde je příklad implementace:

// 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
}

A konečně máme náš efektivní, ale ne tak krátký MongoDB mgo stránkovací funkce. Čtěte dále...

Pomocí github.com/icza/minquery ("snadný" způsob)

Ruční způsob je poměrně zdlouhavý; může být obecně a automatizované . Zde je github.com/icza/minquery vstoupí do obrázku (zveřejnění:Jsem autor ). Poskytuje obal pro konfiguraci a spuštění MongoDB find příkaz, který vám umožní zadat kurzor a po provedení dotazu vám vrátí nový kurzor, který se má použít k dotazu na další dávku výsledků. Obálka je MinQuery typ, který je velmi podobný mgo.Query ale podporuje specifikaci min MongoDB prostřednictvím MinQuery.Cursor() metoda.

Výše uvedené řešení pomocí minquery vypadá takto:

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")

A to je vše. newCursor je kurzor, který se má použít k načtení další dávky.

Poznámka č. 1: Při volání MinQuery.All() , musíte zadat názvy polí kurzoru, z nichž se vytvoří data kurzoru (a nakonec řetězec kurzoru).

Poznámka č. 2: Pokud načítáte částečné výsledky (pomocí MinQuery.Select() ), musíte zahrnout všechna pole, která jsou součástí kurzoru (položka rejstříku), i když je přímo nechcete použít, jinak MinQuery.All() nebude mít všechny hodnoty polí kurzoru, a tak nebude schopen vytvořit správnou hodnotu kurzoru.

Podívejte se na dokument balíčku minquery zde:https://godoc.org/github.com/icza/minquery, je poměrně krátký a doufám, že čistý.




  1. Operátor agregačního potrubí MongoDB $eq

  2. Nejlepší praxe pro udržení mgo sezení

  3. Zpětné volání instance.save() Mongoose.js se nespustí

  4. Spuštění MongoDB na DigitalOcean