文档
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 工具):
- 可视化浏览数据
- 聚合管道图形构建器
- 索引分析和优化建议
- 免费下载:https://www.mongodb.com/products/compass
思考题
- 什么情况下 Embed 会比 Reference 性能更差?(提示:文档大小 vs 读写模式)
$lookup的局限性是什么?为什么 MongoDB 不建议大量使用它?- 学生选课系统如果用纯 Embed 方式设计,会遇到什么问题?