Skip to content

MongoDB 系统学习文档 ​

适合人群:已经了解基础后端开发,想系统掌握 NoSQL 文档数据库、MongoDB 数据建模、CRUD、索引、聚合管道和生产实践的学习者。

学习目标:从集合和文档入门,逐步掌握 MongoDB 的查询语法、嵌入式建模、引用关系、索引、聚合管道、事务、副本集、分片和与关系型数据库的差异。

核对时间:2026-06-25。本文以 MongoDB 官方文档当前信息为基础整理,重点参考 CRUD、Indexes、Aggregation Pipeline、Transactions 和 Data Modeling 文档。

官方资料:


目录 ​

第一篇:基础认知 ​

  1. MongoDB 是什么
  2. SQL 和 NoSQL 的区别
  3. Database、Collection、Document
  4. BSON 和 JSON
  5. 安装与连接

第二篇:CRUD 基础 ​

  1. 插入文档
  2. 查询文档
  3. 更新文档
  4. 删除文档
  5. 条件、排序和分页
  6. 投影 projection

第三篇:数据建模 ​

  1. 文档模型思维
  2. 嵌入式文档
  3. 引用关系
  4. 一对一、一对多、多对多
  5. Schema 设计与校验
  6. 关系型建模和文档建模的区别

第四篇:索引与聚合 ​

  1. 索引基础
  2. 复合索引
  3. 唯一索引、TTL 索引、文本索引
  4. explain 分析
  5. 聚合管道
  6. 常见聚合场景

第五篇:事务、部署与生产实践 ​

  1. MongoDB 事务
  2. 副本集
  3. 分片
  4. Node.js/Mongoose/Prisma 接入
  5. 性能和安全
  6. 常见误区
  7. 学习路线和练习项目

第一篇:基础认知 ​

1. MongoDB 是什么 ​

MongoDB 是一个文档型 NoSQL 数据库。它不使用表和行作为核心模型,而是使用集合和文档。

关系型数据库中的记录可能长这样:

text
users 表
- id
- name
- email

MongoDB 文档可能长这样:

json
{
  "_id": "665f...",
  "name": "Alice",
  "email": "alice@example.com",
  "profile": {
    "age": 28,
    "city": "Shanghai"
  },
  "tags": ["vip", "newsletter"]
}

MongoDB 适合:

  • 文档结构灵活的业务。
  • 内容管理、配置、日志、事件、商品属性等半结构化数据。
  • 读写一个完整业务对象比多表 join 更常见的场景。
  • 快速迭代、字段变化频繁的项目。

但 MongoDB 不是“万能数据库”。如果业务强依赖复杂事务、多表关联、严格关系约束,关系型数据库可能更合适。

2. SQL 和 NoSQL 的区别 ​

SQL 数据库:

  • 表结构明确。
  • 使用 SQL 查询。
  • 强调关系、约束、事务。
  • 常见产品:PostgreSQL、MySQL。

NoSQL 数据库:

  • 数据模型更灵活。
  • 查询语言因产品而异。
  • 常见类型包括文档、键值、列族、图数据库。
  • MongoDB 属于文档数据库。

区别不是“SQL 老,NoSQL 新”。二者解决的问题不同。

关系型思维:

text
用户表 + 订单表 + 订单项表 + 商品表

文档型思维:

text
订单文档中嵌入订单项快照

MongoDB 建模时要先问:应用最常读取的数据形状是什么?

3. Database、Collection、Document ​

MongoDB 层级:

text
MongoDB Deployment
└── database
    └── collection
        └── document

对比关系型数据库:

MongoDBPostgreSQL/MySQL
databasedatabase
collectiontable
documentrow
fieldcolumn
_idprimary key

注意:这是帮助理解的类比,不代表二者完全等价。MongoDB 文档可以嵌套对象和数组,而关系型表更强调行列结构。

4. BSON 和 JSON ​

MongoDB 文档看起来像 JSON,但底层使用 BSON。BSON 是 Binary JSON,支持更多类型,例如:

  • ObjectId
  • Date
  • Decimal128
  • Binary
  • Regular Expression

示例:

js
{
  _id: ObjectId("665f0a..."),
  createdAt: ISODate("2026-06-25T00:00:00Z"),
  price: Decimal128("99.90")
}

学习时可以先把文档理解为 JSON,但生产中要注意 ObjectId、Date、Decimal 等类型的行为。

5. 安装与连接 ​

Docker 示例:

bash
docker run --name mongo-demo \
  -p 27017:27017 \
  -d mongo:8

连接字符串:

text
mongodb://localhost:27017/appdb

使用 MongoDB Shell:

bash
mongosh "mongodb://localhost:27017/appdb"

常用命令:

js
show dbs
use appdb
show collections
db.users.find()

第二篇:CRUD 基础 ​

6. 插入文档 ​

插入单条:

js
db.users.insertOne({
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date()
});

插入多条:

js
db.users.insertMany([
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" }
]);

如果没有提供 _id,MongoDB 会自动生成 ObjectId。

7. 查询文档 ​

查询所有:

js
db.users.find();

按条件查询:

js
db.users.find({
  email: "alice@example.com"
});

比较查询:

js
db.orders.find({
  amount: { $gte: 100 }
});

数组查询:

js
db.users.find({
  tags: "vip"
});

嵌套字段:

js
db.users.find({
  "profile.city": "Shanghai"
});

8. 更新文档 ​

更新一条:

js
db.users.updateOne(
  { email: "alice@example.com" },
  { $set: { name: "Alice Chen" } }
);

更新多条:

js
db.users.updateMany(
  { active: false },
  { $set: { archived: true } }
);

自增:

js
db.products.updateOne(
  { _id: productId },
  { $inc: { stock: -1 } }
);

数组追加:

js
db.users.updateOne(
  { email: "alice@example.com" },
  { $addToSet: { tags: "vip" } }
);

常用更新操作符:

  • $set
  • $unset
  • $inc
  • $push
  • $addToSet
  • $pull

9. 删除文档 ​

删除一条:

js
db.users.deleteOne({
  email: "alice@example.com"
});

删除多条:

js
db.logs.deleteMany({
  createdAt: {
    $lt: new Date("2025-01-01")
  }
});

生产中删除要非常谨慎。对核心业务数据,通常优先考虑软删除:

js
db.users.updateOne(
  { _id: userId },
  { $set: { deletedAt: new Date() } }
);

10. 条件、排序和分页 ​

逻辑条件:

js
db.users.find({
  $or: [
    { role: "admin" },
    { tags: "vip" }
  ]
});

排序:

js
db.posts.find().sort({ createdAt: -1 });

分页:

js
db.posts.find()
  .sort({ createdAt: -1 })
  .skip(40)
  .limit(20);

大分页建议使用游标方式:

js
db.posts.find({
  _id: { $lt: lastSeenId }
})
.sort({ _id: -1 })
.limit(20);

skip 很大时也会有性能问题。

11. 投影 projection ​

只返回指定字段:

js
db.users.find(
  { active: true },
  { name: 1, email: 1 }
);

排除字段:

js
db.users.find(
  {},
  { passwordHash: 0 }
);

接口返回数据时,不要把敏感字段返回给前端。


第三篇:数据建模 ​

12. 文档模型思维 ​

关系型数据库建模通常先分析实体和关系,再拆表。

MongoDB 建模更关注:

  • 应用如何读取数据?
  • 哪些数据总是一起展示?
  • 哪些数据更新频率不同?
  • 文档会不会无限增长?
  • 是否需要跨文档事务?

MongoDB 的核心建模选择:

  1. 嵌入 embedded documents
  2. 引用 references

没有绝对正确答案,要围绕读写模式设计。

13. 嵌入式文档 ​

订单文档示例:

json
{
  "_id": "order1",
  "userId": "user1",
  "status": "paid",
  "items": [
    {
      "productId": "p1",
      "productName": "Keyboard",
      "price": 199.00,
      "quantity": 1
    }
  ],
  "shippingAddress": {
    "province": "Shanghai",
    "city": "Shanghai",
    "detail": "xxx road"
  },
  "createdAt": "2026-06-25T00:00:00Z"
}

适合嵌入:

  • 子数据总是和父数据一起读取。
  • 子数据生命周期依赖父数据。
  • 子数据数量有限。
  • 需要保存历史快照。

例如订单项适合嵌入订单,因为订单项通常和订单一起展示,并且需要保存下单时商品快照。

14. 引用关系 ​

引用用户:

json
{
  "_id": "post1",
  "authorId": "user1",
  "title": "MongoDB 入门"
}

适合引用:

  • 关联数据很大。
  • 关联数据经常独立更新。
  • 多个文档共享同一份数据。
  • 嵌入会导致数据重复过多。
  • 子数据数量可能无限增长。

例如文章引用作者,而不是把完整用户资料嵌入每篇文章。否则用户改头像后,大量文章文档都要更新。

15. 一对一、一对多、多对多 ​

一对一嵌入:

json
{
  "_id": "user1",
  "name": "Alice",
  "profile": {
    "bio": "Frontend developer",
    "avatarUrl": "https://example.com/a.png"
  }
}

一对多嵌入:

json
{
  "_id": "post1",
  "title": "文章",
  "comments": [
    { "userId": "user1", "content": "不错" },
    { "userId": "user2", "content": "学习了" }
  ]
}

如果评论可能很多,不建议无限嵌入文章文档。可以拆成 comments 集合:

json
{
  "_id": "comment1",
  "postId": "post1",
  "userId": "user1",
  "content": "不错"
}

多对多可以使用数组引用:

json
{
  "_id": "post1",
  "tagIds": ["tag1", "tag2"]
}

或使用中间集合:

json
{
  "postId": "post1",
  "tagId": "tag1",
  "createdAt": "2026-06-25T00:00:00Z"
}

如果关系本身有属性,使用中间集合更清晰。

16. Schema 设计与校验 ​

MongoDB 是灵活 schema,不代表没有 schema。你可以在应用层用 TypeScript、Zod、Mongoose schema,也可以在数据库层使用 JSON Schema validation。

示例:

js
db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["email", "name"],
      properties: {
        email: { bsonType: "string" },
        name: { bsonType: "string" },
        age: { bsonType: "int" }
      }
    }
  }
});

建议:

  • 核心业务字段要有校验。
  • 应用层和数据库层都要有基本约束。
  • 字段演进要保留兼容性。
  • 不要让同一集合里出现大量不兼容文档形状。

17. 关系型建模和文档建模的区别 ​

关系型数据库:

text
users
orders
order_items
products

MongoDB:

text
orders 文档里嵌入 order_items 快照
products 单独集合维护当前商品

关系型数据库擅长:

  • 强关系
  • 复杂 join
  • 严格约束
  • 多表事务

MongoDB 擅长:

  • 一个文档表达一个聚合对象
  • 字段灵活
  • 嵌套结构
  • 快速读取完整对象

建模口诀:

text
一起读取、一起变化、数量有限:考虑嵌入。
独立更新、被多处共享、数量很大:考虑引用。

第四篇:索引与聚合 ​

18. 索引基础 ​

创建索引:

js
db.users.createIndex({ email: 1 });

查询:

js
db.users.find({ email: "alice@example.com" });

索引提升查询速度,但增加写入成本和存储空间。

查看索引:

js
db.users.getIndexes();

删除索引:

js
db.users.dropIndex("email_1");

19. 复合索引 ​

创建:

js
db.orders.createIndex({
  userId: 1,
  status: 1,
  createdAt: -1
});

适合查询:

js
db.orders.find({
  userId: "user1",
  status: "paid"
}).sort({ createdAt: -1 });

复合索引字段顺序很重要。通常把等值过滤字段放前面,排序或范围字段放后面,但要结合真实查询模式。

20. 唯一索引、TTL 索引、文本索引 ​

唯一索引:

js
db.users.createIndex(
  { email: 1 },
  { unique: true }
);

TTL 索引用于自动删除过期文档:

js
db.sessions.createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }
);

文本索引:

js
db.posts.createIndex({
  title: "text",
  content: "text"
});

文本搜索:

js
db.posts.find({
  $text: { $search: "mongodb index" }
});

复杂全文搜索场景可以考虑专门搜索引擎或 MongoDB Atlas Search。

21. explain 分析 ​

js
db.orders.find({
  userId: "user1",
  status: "paid"
}).explain("executionStats");

关注:

  • 是否使用索引。
  • 扫描了多少文档。
  • 返回了多少文档。
  • 是否出现 collection scan。

优化原则:

  • 高频过滤字段建立索引。
  • 排序字段和过滤字段协调设计复合索引。
  • 避免返回不必要字段。
  • 大集合查询必须关注 explain。

22. 聚合管道 ​

聚合管道由多个 stage 组成,文档依次经过每个 stage。

示例:统计每个状态的订单金额。

js
db.orders.aggregate([
  { $match: { createdAt: { $gte: new Date("2026-01-01") } } },
  {
    $group: {
      _id: "$status",
      totalAmount: { $sum: "$amount" },
      count: { $sum: 1 }
    }
  },
  { $sort: { totalAmount: -1 } }
]);

常见 stage:

  • $match:过滤
  • $project:选择和计算字段
  • $group:分组
  • $sort:排序
  • $limit:限制数量
  • $skip:跳过
  • $lookup:类似 join
  • $unwind:展开数组

一般把 $match 尽量放前面,减少后续处理的数据量。

23. 常见聚合场景 ​

按天统计:

js
db.orders.aggregate([
  {
    $group: {
      _id: {
        $dateToString: {
          format: "%Y-%m-%d",
          date: "$createdAt"
        }
      },
      count: { $sum: 1 },
      amount: { $sum: "$amount" }
    }
  },
  { $sort: { _id: 1 } }
]);

展开订单项:

js
db.orders.aggregate([
  { $unwind: "$items" },
  {
    $group: {
      _id: "$items.productId",
      quantity: { $sum: "$items.quantity" }
    }
  }
]);

关联查询:

js
db.posts.aggregate([
  {
    $lookup: {
      from: "users",
      localField: "authorId",
      foreignField: "_id",
      as: "author"
    }
  }
]);

$lookup 能做关联,但不要把 MongoDB 当成关系型数据库大量 join。频繁复杂 join 可能说明数据模型不适合 MongoDB。


第五篇:事务、部署与生产实践 ​

24. MongoDB 事务 ​

MongoDB 支持多文档事务,但使用时要谨慎。文档数据库设计应尽量让一个业务聚合能在单文档内原子更新。

单文档更新天然是原子的:

js
db.products.updateOne(
  { _id: productId, stock: { $gt: 0 } },
  { $inc: { stock: -1 } }
);

多文档事务适合:

  • 必须同时更新多个集合。
  • 财务、库存、订单等强一致操作。
  • 不能通过单文档模型解决的一致性需求。

事务会增加复杂度和性能成本。不要因为 MongoDB 支持事务,就把它建模成关系型数据库。

25. 副本集 ​

副本集用于高可用。

text
Primary
├── Secondary
└── Secondary

Primary 处理写入,Secondary 复制数据。Primary 故障时,副本集可以选举新的 Primary。

副本集价值:

  • 高可用
  • 数据冗余
  • 读扩展
  • 支持事务等能力

生产环境不应该只运行单节点 MongoDB。

26. 分片 ​

分片用于水平扩展,把数据分布到多个 shard。

适合:

  • 数据量非常大。
  • 单机存储或写入扛不住。
  • 查询可以通过合理 shard key 分散。

分片关键是 shard key 选择。糟糕的 shard key 会导致热点、数据倾斜和查询低效。

普通项目不要过早分片。先做好索引、数据模型、硬件和副本集。

27. Node.js/Mongoose/Prisma 接入 ​

MongoDB Node.js Driver:

ts
import { MongoClient } from "mongodb";

const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();

const db = client.db("appdb");
const users = db.collection("users");

await users.insertOne({
  name: "Alice",
  email: "alice@example.com",
});

Mongoose:

ts
import mongoose from "mongoose";

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  name: { type: String, required: true },
});

const User = mongoose.model("User", userSchema);

Prisma 也支持 MongoDB,但功能模型和关系型数据库不同。使用 Prisma + MongoDB 时要认真阅读官方限制和数据建模方式,不要套用 PostgreSQL/MySQL 的关系型思维。

28. 性能和安全 ​

性能建议:

  • 高频查询必须建索引。
  • 使用 projection 限制返回字段。
  • 避免无限增长的大数组。
  • 大分页使用游标。
  • 聚合先 $match 再 $group。
  • 用 explain 分析慢查询。

安全建议:

  • 开启认证。
  • 不要把数据库暴露到公网。
  • 使用最小权限账号。
  • 连接字符串不要提交到 Git。
  • 定期备份。
  • 对敏感字段加密或脱敏。

29. 常见误区 ​

误区一:MongoDB 没有 schema,所以可以随便存。

纠正:MongoDB 是灵活 schema,不是无设计。生产集合需要明确文档结构和校验规则。

误区二:MongoDB 不支持事务,所以不能做一致性。

纠正:MongoDB 支持事务,但更推荐通过合理文档模型减少跨文档事务。

误区三:所有关系都应该嵌入。

纠正:数量无限增长、独立更新、被多处共享的数据更适合引用。

误区四:MongoDB 查询一定比 SQL 快。

纠正:性能取决于数据模型、索引、查询模式和数据量。

误区五:MongoDB 可以替代所有关系型数据库。

纠正:强关系、复杂 join、严格约束场景仍然适合 PostgreSQL/MySQL。

30. 学习路线和练习项目 ​

第一阶段:基础 CRUD

  • insertOne
  • find
  • updateOne
  • deleteOne
  • projection

第二阶段:文档建模

  • 嵌入
  • 引用
  • 一对多
  • 多对多
  • 文档增长控制

第三阶段:索引

  • 单字段索引
  • 复合索引
  • 唯一索引
  • TTL 索引
  • explain

第四阶段:聚合

  • $match
  • $group
  • $project
  • $sort
  • $lookup
  • $unwind

第五阶段:生产

  • 副本集
  • 事务
  • 备份
  • 权限
  • 性能监控

练习项目:

text
内容管理系统
- users
- articles
- categories
- comments
- audit_logs

重点练习:

  • 文章文档嵌入部分元数据。
  • 评论数量大时拆成集合。
  • 标签数组索引。
  • 聚合统计阅读量。
  • TTL 清理临时草稿或会话。

快速记忆清单 ​

  • MongoDB 是文档型 NoSQL 数据库,不是 SQL 数据库。
  • Collection 类似表,Document 类似行,但文档可以嵌套对象和数组。
  • 建模要围绕应用读写模式,而不是机械拆表。
  • 一起读取、一起变化、数量有限的数据适合嵌入。
  • 独立更新、共享、数量很大的数据适合引用。
  • 索引对 MongoDB 同样关键,慢查询要看 explain。
  • 聚合管道适合统计、转换和复杂查询。
  • 支持事务不代表应该把 MongoDB 当关系型数据库使用。

基于 VitePress 构建的个人学习文档知识库