
本文详解如何在 Go 中通过 mgo 驱动正确构建 MongoDB 聚合管道(如 $match + $group),重点纠正常见的 BSON 结构嵌套错误,避免因语法格式导致查询返回空结果。
本文详解如何在 go 中通过 mgo 驱动正确构建 mongodb 聚合管道(如 `$match` + `$group`),重点纠正常见的 bson 结构嵌套错误,避免因语法格式导致查询返回空结果。
在 MongoDB 原生 shell 中,聚合管道由多个阶段(如 {$match: {...}}, {$group: {...}})组成的数组表达,各阶段用逗号分隔、独立对象包裹。但在 Go 中使用 mgo 时,必须严格遵循 Go 的语法和 bson.M 的嵌套结构——每个管道阶段是一个 bson.M,而整个管道是 []bson.M 切片,不能将多个阶段合并写在一个 bson.M 内。
你原始代码的问题正在于此:
// ❌ 错误:$match 和 $group 被错误地写在同一层 bson.M 中
pipe := c.Pipe([]bson.M{
{
"$match": bson.M{"_id": bson.M{"$lt": level.ID}},
"$group": bson.M{"_id": "$title", "totalReward": bson.M{"$sum": "$rewardCoins"}}, // ❌ 这里不是独立阶段!
},
})这等价于一个非法的单阶段操作:{ "$match": ..., "$group": ... } —— MongoDB 不识别这种混合写法,因此管道执行失败或静默返回空结果。
✅ 正确写法是:每个聚合阶段作为切片中的独立 bson.M 元素:
pipe := c.Pipe([]bson.M{
{
"$match": bson.M{
"_id": bson.M{"$lt": level.ID},
},
}, // ← 注意此处逗号,表示下一个阶段开始
{
"$group": bson.M{
"_id": "$title",
"totalReward": bson.M{"$sum": "$rewardCoins"},
},
},
})
var res []bson.M
err := pipe.All(&res)
if err != nil {
log.Fatal("Aggregation failed:", err)
}
// res 现在包含聚合结果,例如: [{"_id":"Level 1","totalReward":1000}]? 关键区别对比:
- Shell JSON 格式:[{ $match: ... }, { $group: ... }] → 两个独立对象
- Go []bson.M:[]bson.M{ { "$match": ... }, { "$group": ... } } → 两个独立 map 元素
- ❌ 错误等价于:[]bson.M{ { "$match": ..., "$group": ... } } → 单个 map 含两个键 → 无效管道
? 额外注意事项:
- level.ID 必须是 bson.ObjectId 类型(非字符串),确保已通过 bson.ObjectIdHex() 解析;
- 若需按固定标题(如 "Level 11")分组而非字段值,请将 "$title" 改为 bson.M{"title": "Level 11"},与原始 shell 查询一致;
- pipe.All(&res) 会一次性加载全部结果到内存,大数据集建议使用 pipe.Iter() 流式处理;
- mgo 已归档,生产环境推荐迁移至官方 mongo-go-driver,其聚合 API 更清晰(如 collection.Aggregate(ctx, pipeline))。
掌握 []bson.M 的“阶段切片”本质,是写出可靠 MongoDB 聚合 Go 代码的第一步。


















