MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Wydajne stronicowanie w MongoDB przy użyciu mgo

Niestety mgo.v2 sterownik nie zapewnia wywołań API w celu określenia cursor.min() .

Ale jest rozwiązanie. mgo.Database typ dostarcza Database.Run() metoda do uruchamiania dowolnych poleceń MongoDB. Dostępne polecenia i ich dokumentację można znaleźć tutaj:Polecenia bazy danych

Począwszy od MongoDB 3.2, nowe find dostępne jest polecenie, które może być używane do wykonywania zapytań i obsługuje określanie min argument, który oznacza pierwszy wpis indeksu, od którego rozpocznie się wyświetlanie wyników.

Dobry. To, co musimy zrobić, to po każdej partii (dokumenty strony) wygenerować min dokument z ostatniego dokumentu wyniku zapytania, który musi zawierać wartości wpisu indeksu, który został użyty do wykonania zapytania, a następnie można pobrać następną partię (dokumenty następnej strony) ustawiając ten minimalny wpis indeksu przed do wykonania zapytania.

Ten wpis indeksu – nazwijmy go kursorem od teraz – może być zakodowany jako string i wysyłane do klienta wraz z wynikami, a gdy klient chce mieć następną stronę, odsyła kursor mówiąc, że chce, aby wyniki zaczynały się po tym kursorze.

Zrób to ręcznie ("trudny")

Polecenie do wykonania może mieć różne formy, ale nazwa polecenia (find ) musi być pierwszy w uporządkowanym wyniku, więc użyjemy bson.D (co zachowuje porządek w przeciwieństwie do 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},
    )
}

Wynik wykonania MongoDB find polecenie z Database.Run() można przechwycić za pomocą następującego 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)
}

Mamy teraz wyniki, ale w wycinku typu []bson.Raw . Ale chcemy go w kawałku typu []*User . To tutaj Collection.NewIter() przydaje się. Może przekształcać (odblokowywać) wartość typu []bson.Raw do dowolnego typu, który zwykle przekazujemy do Query.All() lub Iter.All() . Dobry. Zobaczmy:

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

Mamy teraz użytkowników następnej strony. Pozostało tylko jedno:wygenerowanie kursora, który zostanie użyty do pobrania kolejnej strony, jeśli kiedykolwiek będziemy jej potrzebować:

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
}

Wszystko jest w porządku, ale jak przekonwertować cursorData? do string i wzajemnie? Możemy użyć bson.Marshal() i bson.Unmarshal() w połączeniu z kodowaniem base64; użycie base64.RawURLEncoding da nam bezpieczny w sieci ciąg kursora, który można dodać do zapytań URL bez uciekania.

Oto przykładowa implementacja:

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

I wreszcie mamy wydajną, ale nie tak krótką MongoDB mgo funkcjonalność stronicowania. Czytaj dalej...

Korzystanie z github.com/icza/minquery ("łatwy" sposób)

Sposób ręczny jest dość długi; można to uczynić ogólnym i automatycznie . To tutaj github.com/icza/minquery pojawia się na zdjęciu (ujawnienie:jestem autorem ). Zapewnia opakowanie do konfiguracji i wykonania MongoDB find polecenie, które pozwala określić kursor, a po wykonaniu zapytania zwraca nowy kursor, który będzie używany do zapytania następnej partii wyników. Opakowanie to MinQuery typ bardzo podobny do mgo.Query ale obsługuje określanie min MongoDB za pomocą MinQuery.Cursor() metoda.

Powyższe rozwiązanie przy użyciu minquery wygląda tak:

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

I to wszystko. newCursor jest kursorem używanym do pobrania następnej partii.

Uwaga nr 1: Podczas wywoływania MinQuery.All() , musisz podać nazwy pól kursora, zostaną one użyte do zbudowania danych kursora (i ostatecznie ciągu kursora).

Uwaga #2: Jeśli pobierasz częściowe wyniki (za pomocą MinQuery.Select() ), musisz uwzględnić wszystkie pola, które są częścią kursora (wpis indeksu), nawet jeśli nie zamierzasz ich używać bezpośrednio, w przeciwnym razie MinQuery.All() nie będzie miał wszystkich wartości pól kursora, więc nie będzie w stanie utworzyć prawidłowej wartości kursora.

Sprawdź dokumentację pakietu minquery tutaj:https://godoc.org/github.com/icza/minquery, jest raczej krótki i mam nadzieję, że czysty.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Relacje MongoDB:osadzanie czy odwołanie?

  2. Node.js - czekaj na wiele wywołań asynchronicznych

  3. Łączenie z MongoDB 3.0 za pomocą Java Spring

  4. Jak wstawić, jeśli nie istnieje, inna aktualizacja z mongoengine?

  5. Jak usunąć element tablicy w mongodb?