MongoDB不支持原生递归语法,仅通过$graphLookup实现有限深度的图遍历;它需显式指定maxDepth、connectFromField/connectToField,适用于层级≤10的树形结构,且受100MB内存限制,不支持allowDiskUse。

递归查询在MongoDB里没有原生$recursive操作符
直接告诉你结论:MongoDB 本身不支持像 PostgreSQL 的 WITH RECURSIVE 那样的内置递归语法。所有“树形结构递归查询”都得靠应用层模拟,或用聚合管道硬凑——但后者有严格限制,且只适用于深度可控的场景。
用$graphLookup实现有限深度的父子关系展开
$graphLookup 是唯一能近似模拟递归的管道阶段,但它不是真递归,而是广度优先的“图遍历”,必须显式指定最大深度(maxDepth)和终止条件(connectFromField/connectToField)。它适合查组织架构、菜单树、评论回复链这类层级 ≤ 10 的结构。
- 必须确保父子字段命名一致,比如
parentId→_id,否则$graphLookup找不到连接路径 -
maxDepth: 0表示只查直接子节点;maxDepth: 2表示查子、孙、曾孙三级(共4层,含自身) - 结果会把所有匹配节点塞进一个数组字段(默认叫
ancestors或children),不是逐层展开的扁平流 - 如果树太深或节点太多,容易触发内存超限——这时必须加
allowDiskUse: true,否则直接报错errmsg: "Sort exceeded memory limit..."
示例:查某个分类及其所有子分类(最多3级)
db.categories.aggregate([
{ $match: { _id: "tech" } },
{
$graphLookup: {
from: "categories",
startWith: "$_id",
connectFromField: "_id",
connectToField: "parentId",
as: "subtree",
maxDepth: 3
}
}
], { allowDiskUse: true })
为什么不能靠$unwind + $lookup循环模拟无限递归
有人试图用 $unwind 拆出子ID,再 $lookup 查下一层,然后重复这个过程——这在语法上可行,但实际根本走不通:
- 每个
$lookup都是静态的,无法根据上一步结果动态决定查几轮 - 聚合管道阶段数固定,你写5个
$lookup就只能查5层,没法“直到没子节点为止” - 每多一层,输出文档数呈指数爆炸,100个节点深度为5时可能生成上万中间文档
-
$facet或$cond都救不了——它们处理的是分支逻辑,不是循环逻辑
真正可靠的递归方案:应用层迭代或预计算路径
生产环境别硬刚聚合管道。两种更稳的路子:
- 应用代码里用 while 循环 + 多次
find():先查根,再用返回的childrenIds批量查下一层,直到结果为空。控制好并发和超时就行 - 插入/更新时维护一个
path字段,比如"root/tech/backend/nodejs",查子树就用正则{ path: { $regex: "^root/tech/" } }——快、稳、可索引 - 如果必须用聚合返回树形结构,
$graphLookup后接$addFields+$map手动排序层级,但别指望它能处理动态深度超过6的树
最容易被忽略的一点:$graphLookup 的 restrictSearchWithMatch 参数常被漏掉。如果你只想查“启用状态”的子节点,必须在这里加 { status: "active" },而不是放到外面 $match——否则无效过滤会导致遍历浪费大量节点。











