必须用[]bson.d或[]bson.m传给aggregate(), bson.d有序推荐,bson.m无序需慎用;各阶段须独立成bson.d;时间/正则等类型要符合bson规范;需用context控制超时,遍历时正确解码并检查错误。

怎么用 mongo-driver 构建合法的 Pipeline
Go 官方驱动不接受 JSON 字符串或 map[string]interface{} 直接传给 Collection.Aggregate(),必须用 []bson.D 或 []bson.M 表示阶段序列。错用类型会导致编译通过但运行时报 invalid pipeline operator 或空结果。
-
bson.D是有序文档(推荐),适合含多个同名键(如多次$addFields)或依赖顺序的场景(比如$lookup后紧跟$unwind) -
bson.M是无序 map,写起来快,但字段顺序不保证——某些聚合阶段(如$group的_id结构)对字段顺序敏感,可能在旧版 MongoDB 中出错 - 每个阶段必须是独立的
bson.D,不能合并成一个大文档;例如bson.D{{"$match", ...}, {"$sort", ...}}是错的,得拆成[]bson.D{{{"$match", ...}}, {{"$sort", ...}}
常见聚合阶段写法和易错点
写 $match、$group 这类阶段时,Go 里没有 JavaScript 的动态字段语法,所有 key 都得硬编码,且值类型必须匹配 BSON 规范。比如时间范围查询漏转 time.Time,或字符串正则没包 bson.RegEx,都会静默失败。
-
$match:日期字段别直接传time.Now(),要用bson.M{"created_at": bson.M{"$gte": start, "$lt": end}},其中start/end是time.Time -
$group:_id不能是string类型的字段名,得写成bson.D{{"_id", "$status"}};聚合表达式如计数必须用"$sum"而非"count" -
$lookup:from、localField、foreignField全是字符串,但值必须是目标集合真实字段名,拼错不会报错,只返回空数组
执行 Aggregate 并处理结果的最小可靠模式
别跳过错误检查,也别假设 Cursor.All() 总能一次性读完。MongoDB 聚合可能返回大量数据,游标超时或网络中断时,Cursor.Next() 返回 false 不代表结束,得看 Cursor.Err()。
- 始终用
context.WithTimeout()包裹Collection.Aggregate(),避免无限挂起 - 遍历用
for cursor.Next(ctx) { ... },每次循环内必须调用cursor.Decode(&result),不能复用同一个变量地址解码多条(会覆盖) - 聚合结果结构不确定时,用
bson.M接收,但注意嵌套数组/文档里的类型仍是interface{},需要手动断言,比如v, ok := doc["total"].(int32)
性能与兼容性要注意的边界
聚合管道在服务端执行,但 Go 驱动本身不优化阶段顺序或自动加索引提示。有些语法看似合理,实际在 MongoDB 4.2 以下不支持,比如 $function 或带 $expr 的复杂 $match。
立即学习“go语言免费学习笔记(深入)”;
-
$facet在内存中并行执行多个子管道,容易触发Sort exceeded memory limit,务必加$limit或确保分片键参与过滤 - 使用
$merge时,目标集合若不存在,默认不创建,需提前建好或加create: true选项(4.2+) - 驱动版本和 MongoDB 版本要对齐:mongo-go-driver v1.11+ 才完整支持 6.0 的
$setWindowFields,老版本会忽略该阶段
聚合不是万能的,字段太多、嵌套太深、或需要实时 JOIN 多个集合时,先想清楚是不是该拆成多次查询,或者让应用层做轻量合并——驱动再稳,也救不了设计上就超重的 Pipeline。










