物化路径法需路径用|或_分隔、建{path:1}索引、查子树用^开头字面量正则三者严丝合缝,否则$regex退化全表扫描;.会触发嵌套字段歧义,/易被转义,深度超20层致wiredtiger压缩率下降。

path 字段必须用非特殊字符分隔符(如 | 或 _),建 { path: 1 } 索引,查子树时只能用 ^ 开头的字面量正则——三者缺一不可,否则 $regex 就退化为全表扫描。
为什么 path 不能用 . 或 /
MongoDB 把 . 当作嵌套字段分隔符,写 path: "a.b.c" 后,db.coll.find({ "path.a.b": "c" }) 这类误匹配就可能触发;/ 在 URL、HTTP 驱动或某些 shell 环境里会被转义或截断。
实际插入前必须标准化:
• 用 | 替代,例如 "root|node_1|node_3"
• 插入前 .trim() 并去除末尾重复分隔符("root|node_1|" 和 "root|node_1" 是两个不同值)
• 统一小写、去空格,避免 "Root" 和 "root" 被当不同路径
{ path: 1 } 索引为啥没加速 $regex 查询
索引本身没问题,但 MongoDB 只对左锚定(left-anchored)、且以字面量开头的正则才走索引。常见失效写法:
• { path: { $regex: ".*node_1.*" } } —— 中间匹配,不走索引
• { path: /node_1/ } —— JS 字面量正则,无法利用索引
• { path: { $regex: "root|node_1|" } } —— 缺少 ^,不是左锚定
正确写法必须是:{ path: { $regex: "^root\|node_1\|" } }(注意双反斜杠:JS 字符串里一个 \ 会被吃掉)
别拼接变量进正则,比如 ^${parentId}\| —— 动态字符串会让索引失效
深度超过 20 层时 path 字符串会出什么问题
WiredTiger 引擎对单字段 >1024 字节的文档压缩率明显下降,写放大升高;深度超 20 层后,用长名称拼出的 path 很容易突破该阈值。
缓解方式:
• _id 和路径段都用短 ID,比如 "u_8x2" 替代 "user_profile_settings"
• 路径只存可枚举、稳定、无空格的标识符,别塞业务描述字段
• 如果真实业务需要深于 30 层(如 AST、基因谱系),直接换祖先数组方案,别硬撑物化路径
• 定期用 db.tree.aggregate([ { $addFields: { pathLen: { $strLenCP: "$path" } } }, { $sort: { pathLen: -1 } }, { $limit: 5 } ]) 监控最长路径
要不要加 ancestors 数组字段
纯 path 字符串能高效查子树,但没法直接取面包屑或判断“是否属于某祖先”——这两个需求必须靠 ancestors 数组。
• 推荐存 _id 而非 name,避免重命名后数据断裂
• 插入/移动节点时,ancestors 必须和 path 同步更新,漏一次就导致权限或导航错乱
• 建普通索引 { ancestors: 1 } 即可支持高效查询,比如 find({ _id: { $in: doc.ancestors } })
• 如果菜单层级浅(≤5 层)、不需面包屑、也不做祖先判断,可以省掉这个字段











