从零入门到学生管理系统实战

知识库
知识库文档
/tech-stacks/mongodb/tutorial/从零入门到学生管理系统实战.md

文档

1. 什么是 MongoDB?

MongoDB 是一个面向文档(Document-Oriented)的 NoSQL 数据库。不同于 MySQL 的表格行,MongoDB 的数据单元是 BSON 文档(Binary JSON),最接近 JavaScript 对象。

核心概念对照

RDBMS MongoDB
Database Database
Table Collection(集合)
Row Document(文档)
Column Field(字段)
Primary Key _id (自动生成 ObjectId)
JOIN $lookup(聚合管道)

2. 文档模型设计

⚠️ MongoDB 的核心挑战:Embed vs Reference

// 方案 A:内嵌(Embed)- 适合"包含"关系
{
  _id: ObjectId("..."),
  title: "MongoDB 入门",
  author: { name: "张三", email: "z@s.com" },  // 内嵌作者
  tags: ["NoSQL", "数据库"],
  comments: [
    { user: "alice", text: "很棒!", at: ISODate("...") },
    { user: "bob", text: "学习了", at: ISODate("...") }
  ]
}

// 方案 B:引用(Reference)- 适合"多对多"关系
// courses 集合
{ _id: 1, name: "数据库原理", teacher: "王教授" }
// students 集合
{ _id: 1001, name: "小明", course_ids: [1, 2, 3] }

设计原则

  • 一起读的一起存:高频一起查询的用 Embed
  • 独立实体用引用:独立的实体(用户/课程)分开存
  • 数组不要无限增长:内嵌数组有 16MB 文档限制

3. 实战:学生管理系统

use school

// === 1. 集合与索引 ===
db.students.createIndex({ email: 1 }, { unique: true })
db.students.createIndex({ name: 1 })
db.courses.createIndex({ name: 1 }, { unique: true })
db.enrollments.createIndex({ student_id: 1, course_id: 1 }, { unique: true })

// === 2. 插入数据 ===
db.students.insertMany([
  { name: "张三", email: "zs@school.edu", age: 20, gender: "M" },
  { name: "李四", email: "ls@school.edu", age: 21, gender: "M" },
  { name: "王五", email: "ww@school.edu", age: 19, gender: "F" },
])

db.courses.insertMany([
  { name: "数据库原理", credits: 3, teacher: "王教授", capacity: 60 },
  { name: "数据结构", credits: 4, teacher: "李教授", capacity: 50 },
  { name: "操作系统", credits: 3, teacher: "张教授", capacity: 55 },
])

// === 3. 选课(事务 - MongoDB 4.0+) ===
const session = db.getMongo().startSession()
session.startTransaction()
try {
  const enrollments = session.getDatabase("school").enrollments

  enrollments.insertMany([
    { student_id: ObjectId("..."), course_id: ObjectId("..."), enrolled_at: new Date() },
    { student_id: ObjectId("..."), course_id: ObjectId("..."), enrolled_at: new Date() },
  ])

  // 更新课程已选人数
  db.courses.updateOne({ _id: ObjectId("...") }, { $inc: { enrolled: 2 } })

  session.commitTransaction()
} catch (e) {
  session.abortTransaction()
  throw e
} finally {
  session.endSession()
}

// === 4. 聚合:学生成绩单 ===
// 先添加成绩嵌入到 enrollments
db.enrollments.updateMany({}, { $set: { score: Math.floor(Math.random() * 40) + 60 } })

db.enrollments.aggregate([
  {
    $lookup: {
      from: "students",
      localField: "student_id",
      foreignField: "_id",
      as: "student"
    }
  },
  { $unwind: "$student" },
  {
    $lookup: {
      from: "courses",
      localField: "course_id",
      foreignField: "_id",
      as: "course"
    }
  },
  { $unwind: "$course" },
  {
    $group: {
      _id: "$student._id",
      name: { $first: "$student.name" },
      avg_score: { $avg: "$score" },
      courses: { $push: { name: "$course.name", score: "$score" } }
    }
  },
  { $sort: { avg_score: -1 } }
])

4. 索引策略

// 单字段索引
db.students.createIndex({ age: 1 })

// 复合索引
db.enrollments.createIndex({ student_id: 1, course_id: 1 })

// 文本索引(全文搜索)
db.courses.createIndex({ name: "text", teacher: "text" })
db.courses.find({ $text: { $search: "数据库" } })

// 分析查询性能
db.students.find({ age: { $gte: 20 } }).explain("executionStats")

5. MongoDB Compass

除了命令行,强烈推荐安装 MongoDB Compass(GUI 工具):

思考题

  1. 什么情况下 Embed 会比 Reference 性能更差?(提示:文档大小 vs 读写模式)
  2. $lookup 的局限性是什么?为什么 MongoDB 不建议大量使用它?
  3. 学生选课系统如果用纯 Embed 方式设计,会遇到什么问题?

信息

路径
/tech-stacks/mongodb/tutorial/从零入门到学生管理系统实战.md
更新时间
2026/5/31