Go操作MongoDB聚合管道的关键陷阱是阶段顺序错误、bson类型混用、游标未关闭及字段路径混淆;$match须前置以利用索引,统一用bson.D保证顺序,及时Close游标,区分$与$$CURRENT字段引用。

Go 语言操作 MongoDB 聚合管道本身不难,难的是写出来的聚合逻辑跑不出结果、查得慢、或者返回空——根本原因往往不是语法错,而是阶段顺序、字段路径或上下文丢失没被意识到。
$match 阶段必须放在 $unwind 或 $group 前面
很多初学者把 $match 放在 $unwind 后面,以为“先展开再过滤”更直观。但实际会极大拖慢性能,甚至因内存溢出失败。
-
$unwind会把一个含 100 个子项的文档炸成 100 个独立文档;$match再过滤,等于处理了 100 倍数据量 - MongoDB 无法对
$unwind后的临时结构使用索引,而前置的$match可命中status、created_at等字段索引 - 真实错误现象:聚合超时(
context deadline exceeded)、游标自动关闭、返回空结果但无报错
正确顺序示例:
pipeline := []bson.D{
{{"$match", bson.M{"status": "completed", "created_at": bson.M{"$gte": startTime}}}},
{{"$unwind", "$items"}},
{{"$match", bson.M{"items.quantity": bson.M{"$gt": 0}}}}, // 这里是二次过滤,非替代前置 match
}
bson.D 和 bson.M 混用导致字段丢失
Go 驱动中,bson.D 是有序文档(slice),bson.M 是无序 map。聚合阶段对字段顺序敏感(比如 $group 的 _id 字段名必须严格匹配),用错类型会导致阶段失效。
立即学习“go语言免费学习笔记(深入)”;
-
$group阶段若用bson.M{"_id": "$category", "count": bson.D{{"$sum", 1}},$sum表达式会被忽略——因为bson.M不保证键序,驱动可能把"count"当作顶层字段而非累加器 - 推荐统一用
bson.D构建每个阶段,尤其涉及嵌套表达式时 - 常见坑:
bson.M{"$sum": 1}在$group里必须包裹为bson.D{{"$sum", 1}},否则解析失败且无明确错误提示
Go 中处理聚合结果时容易忽略游标生命周期
调用 collection.Aggregate() 返回的是 *mongo.Cursor,它持有网络连接和服务器资源,不手动关闭会导致连接泄漏、后续查询卡死。
- 不能只靠 defer 关闭:如果聚合结果很大,遍历中途 panic,defer 不触发
- 必须显式调用
cursor.Close(ctx),且应在所有分支(包括 error 分支)都执行 - 典型错误写法:
for cursor.Next(ctx) { ... }结束后没 close,下次 Aggregate 就可能报connection pool is full - 安全写法:用
defer cursor.Close(ctx)放在 Next 循环前,哪怕循环提前 break 也能释放
嵌套字段路径写错:$info.name vs $$CURRENT.info.name
聚合表达式里,$字段名 是字段路径,$$CURRENT 是当前文档引用。混淆两者会导致字段取值为 null 或类型错误。
-
$user.profile.email→ 正确,从当前文档取嵌套字段 -
$$CURRENT.user.profile.email→ 错误,$$CURRENT是整个文档,但user是顶级字段,不应再加$$CURRENT - 常被忽略的场景:
$project中用$cond判断嵌套字段是否存在,写成{"$cond": [ {"$eq": ["$$CURRENT.items", null]}, ...]}会永远为 false —— 应该用{"$eq": ["$items", nil]}或{"$ifNull": ["$items", []]}
最隐蔽的问题是:这类错误不会报错,只是静默返回 null,调试时得逐阶段用 $addFields 打印中间结果才能定位。


















