是什么
$graphLookup 在聚合管道里做递归查找:从一个起始值出发,沿着「本集合某字段」与「本集合另一个字段」的连接关系反复匹配,把沿途命中的文档收集到一个数组里。它适合组织汇报链、物料层级、分类树这类单集合内的自引用结构,不用把关系预先拍平或反复查库。
基本信息
| 语法 | { $graphLookup: { from, startWith, connectFromField, connectToField, as, maxDepth, depthField, restrictSearchWithMatch } } |
|---|---|
| from | 被查找的集合,通常就是当前集合自身 |
| startWith | 起始匹配值,可以是字面量、数组或字段表达式 |
| connectFromField / connectToField | 递归的连接关系:用命中文档的某个字段去匹配下一个文档 |
| as | 结果数组字段名 |
| maxDepth | 递归深度上限;不设置表示不限深度,风险很大 |
典型使用场景
- 组织架构:从某个员工出发向上找出全部汇报上级,或向下找出所有下属
- 分类与物料层级:从子类目向上追溯到根类目,用于路径展示与权限继承
- 依赖关系:找出某个服务的全部上游依赖,评估变更影响面
- 路径回溯:在流量、物流链路里沿着引用关系回溯到起点
- 配合过滤:用 restrictSearchWithMatch 只在有效状态的节点上继续递归
递归查找示例
递归查找示例
db.employees.aggregate([
{ $match: { name: "Alice" } }, # 从一个人出发
{ $graphLookup: {
from: "employees",
startWith: "$manager", # 起点:直属上级
connectFromField: "manager", # 命中文档继续沿 manager 往上找
connectToField: "_id",
as: "chain", # 结果放在 chain 数组里
maxDepth: 5, # 必须给上限,避免无界递归
depthField: "level" # 每个节点带出它离起点的层级
} },
{ $project: { name: 1, "chain.name": 1, "chain.level": 1 } }
])
db.categories.aggregate([ # 只在启用状态的节点上继续递归
{ $match: { slug: "laptop" } },
{ $graphLookup: { from: "categories", startWith: "$parent", connectFromField: "parent", connectToField: "_id", as: "ancestors", maxDepth: 8, restrictSearchWithMatch: { enabled: true } } }
])参数逐项读法
| 参数 | 作用 | 注意点 |
|---|---|---|
| from | 递归查找的集合 | 指向本集合即自引用结构 |
| startWith | 起始值或表达式 | 常写成 "$parentField" 形式 |
| connectFromField | 命中文档用于继续匹配的字段 | 写错会立刻断链,结果只剩一层 |
| connectToField | 被匹配的字段 | 通常是 _id 或业务编码 |
| as | 输出数组字段名 | 结果是无序匹配集合,需要时自行排序 |
| maxDepth | 最大递归深度 | 必须设上限,否则可能无限展开 |
| depthField | 记录层级的字段名 | 用于过滤或展示层级 |
| restrictSearchWithMatch | 递归时的额外过滤 | 缩小每层匹配范围,减少无效展开 |
递归深度与匹配范围不设限时开销会快速上升:每一层都要在目标集合上做一次匹配,长链路或宽层级结构会成倍放大工作量。先用 maxDepth 与 restrictSearchWithMatch 把范围框住,再看性能是否可接受。
单集合内的浅层递归用 $graphLookup 很划算;一旦需要多跳变长路径、最短路径或环路检测,这类问题更适合图数据库。使用分片集合作为 from 时限制更多,动手前查对应版本手册。