MongoDB嵌套子分类递归查询过慢,求高效数据库查询方案
用MongoDB原生聚合优化多层嵌套子分类查询性能
你的问题根源在于多次串行数据库查询:原递归函数每处理一个子分类就发起一次MongoDB请求,多层嵌套下会产生大量重复的数据库交互,导致响应延迟(甚至2分钟)。改用MongoDB的$graphLookup聚合阶段+内存树形构建,可以将数据库查询次数从O(n)降到1次,大幅提升性能。
优化方案步骤
1. 添加索引(必做)
首先为category字段创建单键索引,加速查询和递归匹配:
// 在集合初始化时执行一次 await subCategory.createIndex({ category: 1 });
2. 聚合查询+内存构建树形
通过$graphLookup一次性获取根分类及其所有层级的子分类,再在内存中构建嵌套树形结构:
const getNestedSubCategory = async (rootCategory) => { // 第一步:用$graphLookup获取根分类及所有后代 const hierarchyResult = await subCategory.aggregate([ // 匹配根分类 { $match: { category: rootCategory } }, // 递归查询所有子分类,返回所有后代文档 { $graphLookup: { from: "subcategories", // 替换为你的集合实际名称(注意复数) startWith: "$name", connectFromField: "name", connectToField: "category", as: "descendants", maxDepth: 20, // 可选:限制最大递归深度,防止异常数据导致无限循环 depthField: "depth" // 可选:返回每个节点的层级深度,方便调试 } }, // 合并根文档和后代文档为统一数组 { $addFields: { allItems: { $concatArrays: [ [{ name: "$name", _id: "$_id", haveSubCategory: "$haveSubCategory" }], "$descendants" ] } } }, // 只保留需要的字段 { $project: { allItems: 1, _id: 0 } } ]); if (!hierarchyResult.length) return []; const allItems = hierarchyResult[0].allItems; // 第二步:在内存中构建嵌套树形结构 const buildTree = (items, parentName) => { return items .filter(item => item.category === parentName) .map(item => ({ label: item.name, value: item._id.toString(), // 转换为字符串,和原结果格式一致 key: item._id.toString(), children: item.haveSubCategory ? buildTree(items, item.name) : [] })); }; return buildTree(allItems, rootCategory); };
3. 效果对比
- 原方案:每层子分类发起一次查询,3层嵌套(1+10+100)需要111次数据库请求,串行执行总延迟高。
- 优化后:仅1次数据库查询,所有层级数据在内存中处理,响应时间可从分钟级降到毫秒级。
可选:纯聚合管道生成树形结构
如果希望完全在MongoDB端完成树形构建(避免内存处理),可以使用MongoDB 5.0+的$function自定义聚合函数,直接在数据库中递归构建树形:
const getNestedSubCategory = async (rootCategory) => { const result = await subCategory.aggregate([ { $match: { category: rootCategory } }, { $graphLookup: { from: "subcategories", startWith: "$name", connectFromField: "name", connectToField: "category", as: "allItems" } }, { $addFields: { allItems: { $concatArrays: [[ "$$ROOT" ], "$allItems"] } } }, { $project: { tree: { $function: { body: function(items, parentName) { const buildTree = (parent) => { return items .filter(item => item.category === parent) .map(item => ({ label: item.name, value: item._id.toString(), key: item._id.toString(), children: item.haveSubCategory ? buildTree(item.name) : [] })); }; return buildTree(parentName); }, args: ["$allItems", rootCategory], lang: "js" } } } }, { $replaceRoot: { newRoot: { $arrayElemAt: ["$tree", 0] } } } ]); return result; };
注意:$function需要MongoDB版本≥5.0,且开启JavaScript引擎支持,适合数据量较大的场景。
内容的提问来源于stack exchange,提问作者Md Athar
相关产品推荐
相关产品推荐

