MongoDB嵌套结构设计:如何高效获取特定课程的任务列表?
文档结构优化方案
针对单文档因多语言翻译超出MongoDB 16MB限制的问题,同时满足业务需求,推荐拆分独立集合+引用关联的设计方案,具体如下:
1. 拆分核心集合
将Course→Module→Lesson→Topic→Tasks的层级结构拆分为5个独立MongoDB集合,每个集合存储对应层级的数据,通过ObjectId关联父级,同时内置多语言字段解决翻译需求:
courses集合
{ _id: ObjectId("a..."), name: { en: "How to cook borscht", zh: "如何制作罗宋汤" }, createdAt: ISODate("2024-01-01T00:00:00Z") // 课程元数据 }
modules集合
{ _id: ObjectId("b..."), courseId: ObjectId("a..."), // 关联所属课程 title: { en: "Analyzing the composition", zh: "食材成分分析" }, order: 1 // 控制模块排序顺序 }
lessons集合
{ _id: ObjectId("c..."), moduleId: ObjectId("b..."), // 关联所属模块 title: { en: "Selecting the Cabbage", zh: "挑选卷心菜" }, order: 1 }
topics集合
{ _id: ObjectId("d..."), lessonId: ObjectId("c..."), // 关联所属课时 title: { en: "White Cabbage", zh: "白卷心菜" }, order: 1 // 控制Topic排序,用于定位首个Topic }
tasks集合
{ _id: ObjectId("e..."), topicId: ObjectId("d..."), // 关联所属主题 description: { en: "White cabbage is a common vegetable crop...", zh: "白卷心菜是常见蔬菜作物,属于芸薹属的栽培变种..." } }
user_activities集合(单独存储用户行为)
{ _id: ObjectId("f..."), userId: ObjectId("u..."), targetType: "task", // 可选值:course/module/lesson/topic/task targetId: ObjectId("e..."), action: "completed", // 可选值:completed/viewed timestamp: ISODate("2024-01-02T10:30:00Z") }
2. 核心需求实现方式
(1)/api/courses 返回课程大纲
使用MongoDB聚合查询,通过$lookup关联各级数据,按需组装大纲并返回指定语言内容:
db.courses.aggregate([ { $lookup: { from: "modules", localField: "_id", foreignField: "courseId", as: "modules", pipeline: [ { $sort: { order: 1 } }, { $lookup: { from: "lessons", localField: "_id", foreignField: "moduleId", as: "lessons", pipeline: [ { $sort: { order: 1 } }, { $lookup: { from: "topics", localField: "_id", foreignField: "lessonId", as: "topics", pipeline: [ { $sort: { order: 1 } }, { $lookup: { from: "tasks", localField: "_id", foreignField: "topicId", as: "tasks" } } ] } } ] } } ] } }, // 过滤返回中文内容 { $project: { "name": "$name.zh", "modules.title": "$modules.title.zh", "modules.lessons.title": "$modules.lessons.title.zh", "modules.lessons.topics.title": "$modules.lessons.topics.title.zh", "modules.lessons.topics.tasks.description": "$modules.lessons.topics.tasks.description.zh", "modules.order": 1, "modules.lessons.order": 1, "modules.lessons.topics.order": 1 } } ])
(2)获取指定Lesson的首个Topic下所有任务
通过两步查询快速定位目标数据:
// 1. 找到指定Lesson的首个Topic(按order排序) const firstTopic = await db.topics.findOne( { lessonId: ObjectId("指定lesson的_id") }, { sort: { order: 1 } } ); // 2. 获取该Topic下的所有任务 const tasks = await db.tasks.find( { topicId: firstTopic._id }, { "description.zh": 1 } // 按需返回对应语言 ).toArray();
(3)用户完成行为记录
直接向user_activities集合插入文档即可,查询用户完成情况时,通过userId+targetType+action过滤即可快速获取结果。
3. 性能优化建议
- 给每个集合的关联字段添加单字段索引:比如
modules.courseId、lessons.moduleId、topics.lessonId、tasks.topicId,加速关联查询。 - 给
user_activities添加复合索引:{ userId: 1, targetType: 1, action: 1 },提升用户行为记录的查询效率。 - 若课程大纲访问频繁,可将组装好的大纲缓存到Redis中,减少MongoDB聚合查询的压力。
4. 方案优势
- 彻底解决单文档因多语言翻译超出16MB限制的问题,每个文档体积小巧易维护。
- 查询灵活:可单独查询任意层级的数据,无需加载整个课程文档,性能更优。
- 扩展性强:后续新增层级或修改内容时,仅需调整对应集合,不影响整体结构。
内容的提问来源于stack exchange,提问作者Christopher Robin
相关产品推荐
相关产品推荐

