MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Paging yang efisien di MongoDB menggunakan mgo

Sayangnya mgo.v2 driver tidak menyediakan panggilan API untuk menentukan cursor.min() .

Tetapi ada sebuah solusi. mgo.Database type menyediakan Database.Run() metode untuk menjalankan perintah MongoDB apa pun. Perintah yang tersedia dan dokumentasinya dapat ditemukan di sini:Perintah database

Dimulai dengan MongoDB 3.2, find baru tersedia perintah yang dapat digunakan untuk mengeksekusi kueri, dan mendukung penetapan min argumen yang menunjukkan entri indeks pertama untuk memulai hasil daftar.

Bagus. Yang perlu kita lakukan adalah setelah setiap batch (dokumen halaman) menghasilkan min dokumen dari dokumen terakhir hasil kueri, yang harus berisi nilai entri indeks yang digunakan untuk menjalankan kueri, dan kemudian kumpulan berikutnya (dokumen halaman berikutnya) dapat diperoleh dengan menyetel entri indeks min ini sebelumnya untuk mengeksekusi kueri.

Entri indeks ini – sebut saja kursor mulai sekarang– dapat dikodekan ke string dan dikirim ke klien bersama dengan hasilnya, dan ketika klien menginginkan halaman berikutnya, dia mengirim kembali kursor mengatakan dia ingin hasil dimulai setelah kursor ini.

Melakukannya secara manual (cara "sulit")

Perintah yang akan dieksekusi bisa dalam bentuk yang berbeda, tetapi nama perintahnya (find ) harus menjadi yang pertama di hasil marshaled, jadi kita akan menggunakan bson.D (yang menjaga ketertiban berbeda dengan 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},
    )
}

Hasil eksekusi find Mon MongoDB perintah dengan Database.Run() dapat ditangkap dengan tipe berikut:

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

Kami sekarang memiliki hasilnya, tetapi dalam sepotong jenis []bson.Raw . Tapi kami menginginkannya dalam potongan tipe []*User . Di sinilah Collection.NewIter() berguna. Itu dapat mengubah (menghapus) nilai tipe []bson.Raw ke dalam jenis apa pun yang biasanya kami berikan ke Query.All() atau Iter.All() . Bagus. Mari kita lihat:

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

Kami sekarang memiliki pengguna halaman berikutnya. Hanya satu hal yang tersisa:membuat kursor yang akan digunakan untuk mendapatkan halaman berikutnya jika kita membutuhkannya:

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
}

Ini semua bagus, tetapi bagaimana kita mengonversi cursorData ke string dan sebaliknya? Kami dapat menggunakan bson.Marshal() dan bson.Unmarshal() dikombinasikan dengan pengkodean base64; penggunaan base64.RawURLEncoding akan memberi kita string kursor aman-web, yang dapat ditambahkan ke kueri URL tanpa keluar.

Berikut contoh implementasinya:

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

Dan kami akhirnya memiliki mgo MongoDB kami yang efisien, tetapi tidak terlalu pendek fungsionalitas halaman. Baca terus...

Menggunakan github.com/icza/minquery (cara "mudah")

Cara manual cukup panjang; itu bisa dibuat umum dan otomatis . Di sinilah github.com/icza/minquery muncul dalam gambar (pengungkapan:Saya penulisnya ). Ini menyediakan pembungkus untuk mengonfigurasi dan menjalankan find Mon MongoDB perintah, memungkinkan Anda untuk menentukan kursor, dan setelah menjalankan kueri, itu memberi Anda kembali kursor baru untuk digunakan untuk kueri kumpulan hasil berikutnya. Pembungkusnya adalah MinQuery jenis yang sangat mirip dengan mgo.Query tetapi mendukung menentukan min Mon MongoDB melalui MinQuery.Cursor() metode.

Solusi di atas menggunakan minquery terlihat seperti ini:

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

Dan itu saja. newCursor adalah kursor yang akan digunakan untuk mengambil kumpulan berikutnya.

Catatan #1: Saat memanggil MinQuery.All() , Anda harus memberikan nama bidang kursor, ini akan digunakan untuk membuat data kursor (dan akhirnya string kursor).

Catatan #2: Jika Anda mengambil sebagian hasil (dengan menggunakan MinQuery.Select() ), Anda harus menyertakan semua bidang yang merupakan bagian dari kursor (entri indeks) meskipun Anda tidak bermaksud menggunakannya secara langsung, jika tidak MinQuery.All() tidak akan memiliki semua nilai bidang kursor, sehingga tidak akan dapat membuat nilai kursor yang tepat.

Lihat dokumen paket minquery di sini:https://godoc.org/github.com/icza/minquery, ini agak pendek dan mudah-mudahan bersih.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Simpan Subset Koleksi MongoDB ke Koleksi Lain

  2. Fungsi kustom menghitung kolom proyeksi mongodb

  3. Hitung nilai rata-rata dokumen mongodb

  4. Pembaruan MongoDB dengan syarat

  5. Cara menganalisis penggunaan disk dari wadah Docker