0%

深入理解 MongoDB $graphLookup

让我们通过一个具体的例子来详细解释 get_subtree 函数如何使用 MongoDB 的 $graphLookup 操作来查询文件夹子树。

示例数据

假设我们有以下文件夹结构:

1
2
3
4
5
6
Root (id: 1)
├── Documents (id: 2)
│ ├── Work (id: 3)
│ └── Personal (id: 4)
└── Photos (id: 5)
└── 2023 (id: 6)

对应的 MongoDB 文档:

1
2
3
4
5
6
7
8
[
{ "_id": 1, "name": "Root", "parent": null, "path": "/", "is_root": true },
{ "_id": 2, "name": "Documents", "parent": 1, "path": "/Documents/" },
{ "_id": 3, "name": "Work", "parent": 2, "path": "/Documents/Work/" },
{ "_id": 4, "name": "Personal", "parent": 2, "path": "/Documents/Personal/" },
{ "_id": 5, "name": "Photos", "parent": 1, "path": "/Photos/" },
{ "_id": 6, "name": "2023", "parent": 5, "path": "/Photos/2023/" }
]

查询示例 1:获取整个 Documents 子树

1
get_subtree(folder_id=2)  # 查询 Documents 文件夹的完整子树

聚合管道执行过程:

  1. $match 阶段:首先找到 ID 为 2 的文档

    1
    { "_id": 2, "name": "Documents", "parent": 1, "path": "/Documents/" }
  2. $graphLookup 阶段:递归查找所有子文件夹

    • _id: 2 开始
    • 查找 parent 字段等于当前文档 _id 的所有文档
    • 递归过程:
      • 第一层:找到 _id: 3 (parent=2) 和 _id: 4 (parent=2)
      • 第二层:尝试找 parent=3 和 parent=4 的文档,但没有找到
    • 结果收集到 children 数组中

最终返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"_id": 2,
"name": "Documents",
"parent": 1,
"path": "/Documents/",
"children": [
{
"_id": 3,
"name": "Work",
"parent": 2,
"path": "/Documents/Work/"
},
{
"_id": 4,
"name": "Personal",
"parent": 2,
"path": "/Documents/Personal/"
}
]
}

查询示例 2:限制查询深度

1
get_subtree(folder_id=1, depth=2)  # 查询 Root 文件夹,深度为2

聚合管道执行过程:

  1. $match 阶段:找到 ID 为 1 的文档 (Root)
  2. $graphLookup 阶段:
    • maxDepth: 1 (因为 depth=2,maxDepth=depth-1)
    • 递归过程:
      • 第一层:找到 _id: 2 (parent=1) 和 _id: 5 (parent=1)
      • 第二层:不继续查找,因为达到了 maxDepth

最终返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"_id": 1,
"name": "Root",
"parent": null,
"path": "/",
"is_root": true,
"children": [
{
"_id": 2,
"name": "Documents",
"parent": 1,
"path": "/Documents/"
},
{
"_id": 5,
"name": "Photos",
"parent": 1,
"path": "/Photos/"
}
]
}

查询示例 3:查询单层结构

1
get_subtree(folder_id=5)  # 查询 Photos 文件夹

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_id": 5,
"name": "Photos",
"parent": 1,
"path": "/Photos/",
"children": [
{
"_id": 6,
"name": "2023",
"parent": 5,
"path": "/Photos/2023/"
}
]
}

关键参数解释

  1. from: 要查询的集合名称(在此例中是 “folder”)
  2. startWith: 开始递归查询的起点值(通常是当前文档的 _id
  3. connectFromField: 当前文档中用于连接的字段(通常是 _id
  4. connectToField: 目标文档中用于连接的字段(在此例中是 parent
  5. as: 存储查询结果的数组字段名
  6. maxDepth: 递归的最大深度(0表示只查直接子项)

性能考虑

  1. 对于大型树结构,$graphLookup 可能会消耗较多资源
  2. 可以通过添加索引优化:
    1
    2
    # 确保 parent 字段有索引
    Folder.create_index([("parent", 1)])
  3. 对于非常深的树结构,考虑使用 maxDepth 限制查询深度

这种查询方式相比原来的嵌套树结构,在并发环境下性能更好,因为不再需要锁定整个树结构,而是可以单独查询和修改各个文件夹节点。

宇宙山河浪漫,赞赏动力无限

Welcome to my other publishing channels