$unwind是MongoDB聚合管道中解析嵌套数组的必需操作,它将数组字段拆分为独立文档,使后续阶段可逐元素处理;不使用$unwind而直接引用嵌套路径(如tags.0.name)会导致静默失败或空结果。

聚合管道中解析嵌套数组必须用 $unwind
MongoDB 不会自动展开数组字段;若直接在 $match 或 $project 中引用 tags.0.name 这类路径,查询会静默失败或返回空结果。真正起作用的是 $unwind —— 它把每个数组元素拆成独立文档,后续阶段才能逐个处理。
常见错误现象:用 db.collection.find({ "items.name": "book" }) 能查到数据,但换成 db.collection.aggregate([ { $match: { "items.name": "book" } } ]) 却没结果。这是因为聚合中字段路径匹配逻辑和 find 不同,且未展开时数组整体被视为一个值。
-
$unwind默认遇到空数组或 null 会丢弃整条文档,加preserveNullAndEmptyArrays: true可保留 - 多次
$unwind处理多层嵌套(如orders.items.sku)时,顺序不能错,否则产生笛卡尔积 - 用
$unwind后务必跟$group或$replaceRoot收口,否则输出文档数远超原始数量
$map 和 $reduce 适合数组内计算,不适合“提取”
如果目标是把嵌套数组里的某个字段批量提取出来(比如所有 comments.author.id 拼成字符串),$map + $reduce 是对的;但如果想对每个 comment 单独过滤、关联、排序,就必须先 $unwind。
典型误用:{ $project: { authors: { $map: { input: "$comments", as: "c", in: "$$c.author.name" } } } } —— 这能生成数组,但无法再对每个 author 做 $lookup 或 $sort。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
-
$map返回新数组,不改变文档结构层级 -
$reduce适合归约(如求和、拼接),但无法引入外部集合或条件分支 - MongoDB 5.0 不支持
$function在聚合中写 JS,别指望用自定义逻辑绕过$unwind
副本集环境下聚合性能与读偏好需显式指定
副本集默认只从主节点执行聚合,尤其含 $lookup 或大数据量 $unwind 时,容易打满主节点 CPU。你得主动告诉驱动:允许从从节点读。
错误配置下,即使副本集有 3 个节点,db.collection.aggregate(...) 也永远只走 primary —— 因为 mongosh 和多数驱动默认 readPreference=primary。
- 在 mongosh 中加
.readPref("secondaryPreferred"):db.orders.aggregate([...]).readPref("secondaryPreferred") - Go 驱动里要设
options.Aggregate().SetReadPreference(readpref.SecondaryPreferred()) - 注意:含写操作(如
$out、$merge)的聚合必须走 primary,读偏好无效
用 $lookup 关联嵌套数组引用时,localField 必须是数组
假设 users 文档里存了 roleIds: ["abc", "def"],想查出对应角色名,$lookup 的 localField 得直接写 "roleIds",而不是 "roleIds.0" 或包装成 ["$$roleIds"]。
常见报错:"localField" must be a string or array,其实是传了表达式但类型不对;或者查不到数据,是因为没在 $unwind 后做 $lookup,导致 localField 匹配不上 foreignField。
- 如果数组字段是 ObjectId 字符串(如
"507f1f77bcf86cd799439011"),foreignField对应集合的_id类型必须一致 —— MongoDB 5.0 不会自动转换字符串为 ObjectId - 跨副本集做
$lookup(如分片集群间)不被支持,只能在单副本集或分片内完成 - 用
$lookup+$unwind后,记得用$addFields把关联字段提上来,否则还在roles数组里埋着
$unwind 后文档膨胀不可逆,且副本集读写分离策略不会自动适配聚合阶段**。你得自己决定——该不该展开、在哪展开、展开后要不要立即 $group 收拢,而不是依赖“语法能跑通”就认为逻辑正确。

















