$unwind处理嵌套数组需设preservenullandemptyarrays: true以防丢失空/缺失字段文档;固定深度用多次$unwind+$project拼路径,深树宜应用层循环或预计算;禁混用1与表达式于同一$project;必加allowdiskuse: true防sort内存溢出。

$unwind处理嵌套数组时必须预判空值和缺失字段
树形结构在MongoDB里常以嵌套数组形式存在(比如children字段),直接$unwind会丢弃children: []或children: null的文档——这不是bug,是默认行为。你看到聚合结果变少,大概率卡在这一步。
- 加
preserveNullAndEmptyArrays: true参数:确保父节点即使没子节点也保留,避免数据“消失” - 别在
$unwind前用$match筛children.0,这种写法在聚合里不生效;要用$expr配合$gt或$size判断数组长度 - 如果树有深度不确定(比如无限级分类),单次
$unwind只能展开一级,需配合$reduce或递归应用管道(但7.0不支持原生递归,得靠应用层循环或预计算路径)
$graphLookup不适合深树扁平化,优先用$unwind + $project组合
$graphLookup看着适合树形查询,但它返回的是“匹配到的所有祖先/后代”集合,不是扁平化后的行记录。你要的是“每个叶子节点一行,并带完整路径”,它给的是“每个根节点带一个from数组”,方向反了。
- 对固定深度(如最多3级),用多次
$unwind+$project拼接路径字段更可控:比如path: { $concat: ["$name", "/", "$children.name"] } - 若要保留层级信息,
$project里加计数器字段:level: { $add: [ { $cond: [ { $eq: ["$children", null] }, 0, 1 ] }, 1 ] } -
$graphLookup在7.0副本集上受readConcern限制,若主节点延迟高,可能查不到最新子节点——这点容易被忽略
$project重命名+拼接路径时,字段名不能混用1和表达式
扁平化后常要输出id、name、parent_id、full_path这类字段,$project阶段一写错就整个stage报错:“field specification must be an object or a string”。
- 所有字段必须显式声明,
_id: 0只删_id,其他未列字段自动丢弃 - 禁止这样写:
parent_id: 1, full_path: { $concat: ["$name", "/"] }——1和表达式不能共存于同一$project对象中 - 路径拼接优先用
$reduce处理多层嵌套:full_path: { $reduce: { input: "$ancestors", initialValue: "$name", in: { $concat: ["$$value", "/", "$$this.name"] } } }
副本集环境下聚合性能瓶颈常出在$sort和内存限制
树形扁平化后文档量剧增(一个5层树展开可能生成上百条),$sort阶段极易触发Sort exceeded memory limit错误,尤其在副本集从节点配置偏低时。
- 必须加
allowDiskUse: true选项,否则聚合直接失败 -
$sort尽量放在$limit之后——先取前N条再排序,比全量排序快得多 - 在副本集里,
$lookup关联父节点信息时,确保localField和foreignField都建了索引,否则主节点CPU飙升,从节点同步延迟拉大
preserveNullAndEmptyArrays或忘了allowDiskUse,调试时根本看不到错误,只看到结果为空或超时。











