icz*_*cza 8 pagination go mongodb mongodb-query mgo
我已经搜索过,并没有找到问题的Go解决方案,没有使用或不使用mgo.v2,不是在StackOverflow而不是在任何其他网站上.此问答是本着知识共享/记录的精神.
假设我们users在MongoDB中有一个使用此Go建模的集合struct:
type User struct {
ID bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
Country string `bson:"country"`
}
Run Code Online (Sandbox Code Playgroud)
我们希望根据某些条件对用户进行排序和列出,但由于预期的长结果列表而实施了分页.
为了实现某些查询的结果的分页,MongoDB的和mgo.v2驱动包内置了支持的形式Query.Skip()和Query.Limit(),例如:
session, err := mgo.Dial(url) // Acquire Mongo session, handle error!
c := session.DB("").C("users")
q := c.Find(bson.M{"country" : "USA"}).Sort("name", "_id").Limit(10)
// To get the nth page:
q = q.Skip((n-1)*10)
var users []*User
err = q.All(&users)
Run Code Online (Sandbox Code Playgroud)
然而,如果页面数量增加,这会变慢,因为MongoDB不能"神奇地"跳转到结果中的第x 个文档,它必须遍历所有结果文档并省略(不返回)x需要的第一个文档.跳过.
MongoDB提供了正确的解决方案:如果查询对索引进行操作(它必须处理索引),cursor.min()则可以使用指定第一个索引条目来开始列出结果.
这个Stack Overflow答案展示了如何使用mongo客户端完成它:如何使用MongoDB中的范围查询进行分页?
注意:上述查询所需的索引是:
db.users.createIndex(
{
country: 1,
name: 1,
_id: 1
}
)
Run Code Online (Sandbox Code Playgroud)
但是有一个问题:mgo.v2包没有指定这个的支持min().
我们如何cursor.min()使用mgo.v2驱动程序实现使用MongoDB 功能的高效分页?
icz*_*cza 18
不幸的是,mgo.v2驱动程序不提供API调用来指定cursor.min().
但有一个解决方案.该mgo.Database类型提供了Database.Run()一种运行任何MongoDB命令的方法.可以在此处找到可用的命令及其文档:数据库命令
从MongoDB 3.2开始find,可以使用一个新命令来执行查询,它支持指定min参数,该参数表示开始列出结果的第一个索引条目.
好.我们需要做的是在每个批处理(页面min文档)之后从查询结果的最后一个文档生成文档,该文档必须包含用于执行查询的索引条目的值,然后是下一批(可以通过在执行查询之前设置该最小索引条目来获取下一页的文档.
该索引条目-让我们把它叫做光标从现在开始导通可以被编码的string,并与结果一起发送到客户端,并且在客户端需要下一个页面,他发回光标说,他希望这个结果光标之后开始.
要执行的命令可以采用不同的形式,但命令name(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},
)
}
Run Code Online (Sandbox Code Playgroud)
可以使用以下类型捕获执行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)
}
Run Code Online (Sandbox Code Playgroud)
我们现在有了结果,但是在一个类型中[]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)
Run Code Online (Sandbox Code Playgroud)
我们现在有下一页的用户.只剩下一件事:如果我们需要它,生成用于获取后续页面的光标:
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
}
Run Code Online (Sandbox Code Playgroud)
这一切都很好,但我们如何转换cursorData到string,反之亦然?我们可以使用bson.Marshal()并bson.Unmarshal()结合base64编码; 使用base64.RawURLEncoding会给我们一个web安全的游标字符串,可以添加到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
}
Run Code Online (Sandbox Code Playgroud)
我们终于拥有了高效但不那么短的MongoDB mgo分页功能.继续阅读......
github.com/icza/minquery("简单"的方式)手动方式很漫长; 它可以是通用的和自动化的.这就是github.com/icza/minquery图片所在(披露:我是作者).它提供了一个包装器来配置和执行MongoDB find命令,允许您指定游标,并在执行查询后,它会返回用于查询下一批结果的新游标.包装器是MinQuery非常类似的类型,mgo.Query但它支持min通过该MinQuery.Cursor()方法指定MongoDB .
上面的解决方案使用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")
Run Code Online (Sandbox Code Playgroud)
就这样.newCursor是用于获取下一批次的游标.
注意#1:调用时MinQuery.All(),必须提供光标字段的名称,这将用于构建光标数据(最终是光标字符串).
注意#2:如果你正在检索部分结果(通过使用MinQuery.Select()),你必须包括光标一部分的所有字段(索引条目),即使你不打算直接使用它们,否则MinQuery.All()不会有光标字段的所有值,因此它将无法创建正确的光标值.
查看minquery这里的软件包文档:https://godoc.org/github.com/icza/minquery,它很短,希望干净.
| 归档时间: |
|
| 查看次数: |
6648 次 |
| 最近记录: |