MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Ефективно пейджинг в MongoDB с помощта на mgo

За съжаление mgo.v2 драйверът не предоставя API извиквания за указване на cursor.min() .

Но има решение. mgo.Database type предоставя Database.Run() метод за изпълнение на всякакви команди на MongoDB. Наличните команди и тяхната документация можете да намерите тук:Команди на базата данни

Започвайки с MongoDB 3.2, ново find е налична команда, която може да се използва за изпълнение на заявки и поддържа задаване на min аргумент, който обозначава първия запис в индекса, от който започва да се изброяват резултати.

Добре. Това, което трябва да направим, е след всяка партида (документи на страница) да генерираме min документ от последния документ на резултата от заявката, който трябва да съдържа стойностите на записа в индекса, който е бил използван за изпълнение на заявката, а след това следващата партида (документите на следващата страница) може да бъде получена чрез задаване на този мин. запис на индекса преди за изпълнение на заявката.

Този индексен запис – нека го наречем курсор от сега нататък – може да се кодира в string и се изпраща на клиента заедно с резултатите и когато клиентът иска следващата страница, той изпраща обратно курсора казва, че иска резултатите да започват след този курсор.

Правете го ръчно („трудният“ начин)

Командата, която трябва да се изпълни, може да бъде в различни форми, но името на командата (find ) трябва да бъде първи в маршалирания резултат, така че ще използваме bson.D (който запазва реда за разлика от 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},
    )
}

Резултатът от изпълнение на MongoDB find команда с Database.Run() могат да бъдат заснети със следния тип:

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

Вече имаме резултатите, но в срез от тип []bson.Raw . Но ние го искаме в отрязък от тип []*User . Това е мястото, където Collection.NewIter() идва удобен. Може да трансформира (демаршали) стойност от тип []bson.Raw във всеки тип, който обикновено предаваме на Query.All() или Iter.All() . Добре. Да го видим:

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

Вече имаме потребителите на следващата страница. Остава само едно:генериране на курсора, който да се използва за получаване на следващата страница, ако някога се нуждаем от нея:

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
}

Всичко това е добре, но как да конвертираме cursorData към string и обратно? Можем да използваме bson.Marshal() и bson.Unmarshal() комбиниран с base64 кодиране; използването на base64.RawURLEncoding ще ни даде безопасен в мрежата низ на курсора, който може да се добавя към URL заявки, без да се избягва.

Ето примерна реализация:

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

И най-накрая имаме нашия ефективен, но не толкова кратък MongoDB mgo функционалност за пейджинг. Прочетете...

Използване на github.com/icza/minquery („лесният“ начин)

Ръчният начин е доста дълъг; може да бъде направено общо и автоматизирани . Това е мястото, където github.com/icza/minquery идва в картината (разкриване:аз съм авторът ). Той предоставя обвивка за конфигуриране и изпълнение на MongoDB find команда, която ви позволява да зададете курсор и след изпълнение на заявката ви връща новия курсор, който да се използва за заявка на следващата партида резултати. Обвивката е MinQuery тип, който е много подобен на mgo.Query но поддържа задаване на min на MongoDB чрез MinQuery.Cursor() метод.

Горното решение с помощта на minquery изглежда така:

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

И това е всичко. newCursor е курсорът, който ще се използва за извличане на следващата партида.

Забележка №1: При извикване на MinQuery.All() , трябва да предоставите имената на полетата на курсора, това ще се използва за изграждане на данните за курсора (и в крайна сметка низа на курсора).

Забележка №2: Ако извличате частични резултати (чрез използване на MinQuery.Select() ), трябва да включите всички полета, които са част от курсора (записът в индекса), дори ако не възнамерявате да ги използвате директно, иначе MinQuery.All() няма да има всички стойности на полетата на курсора и така няма да може да създаде правилната стойност на курсора.

Вижте пакетния документ на minquery тук:https://godoc.org/github.com/icza/minquery, доста е кратък и да се надяваме чист.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB на Android

  2. MongoDB $dateToString

  3. Как да разположите ClusterControl на AWS, за да управлявате вашата облачна база данни

  4. Проста реализация на маркиране с MongoDB

  5. Как да изпълним собствена заявка на MongoDB (JSON) само с помощта на mongo-java-driver?