08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
[MongoDB官网地址](https://www.mongodb.com/)
[MongoDB项目地址](https://github.com/mongodb/mongo)

View File

@@ -0,0 +1,210 @@
# 分片Sharding
当数据量增长到单个副本集无法承载或者写操作的吞吐量达到单台主节点的极限时就需要通过分片Sharding来进行水平扩展。本章节将深入探讨 MongoDB 分片集群的架构、核心组件、分片键的选择策略以及如何部署和管理一个分片集群。
---
## 分片概述
### 什么是分片?
分片是一种将大型数据集水平分区到多个服务器或分片上的数据库架构模式。每个分片都是一个独立的副本集存储着整个数据集的一部分。通过分片MongoDB 可以将读写负载分布到多个服务器上,从而实现近乎无限的水平扩展能力。
### 为什么需要分片?
1. **存储容量扩展**: 当数据量超过单台服务器的磁盘容量时,可以通过增加分片来扩展存储空间。
2. **读写吞吐量提升**: 通过将负载分布到多个分片,可以显著提高整个集群的读写处理能力。
3. **高可用性**: 分片集群的每个分片本身就是一个副本集,因此继承了副本集的高可用性特性。
---
## 分片集群架构
一个 MongoDB 分片集群由以下三个核心组件构成:
1. **分片 (Shard)**
- **作用**: 存储数据的单元。每个分片都是一个独立的 MongoDB 副本集,以保证其高可用性。
- **职责**: 存储集合数据的一个子集Chunk
2. **查询路由 (Query Router / `mongos`)**
- **作用**: 客户端的入口。`mongos` 是一个轻量级的无状态进程,它接收客户端的请求,并将其路由到正确的分片上。
- **职责**: 从配置服务器获取元数据,根据分片键将查询路由到目标分片,并聚合来自多个分片的结果返回给客户端。
3. **配置服务器 (Config Server)**
- **作用**: 存储集群的元数据。这些元数据包含了数据在各个分片上的分布情况(哪个 Chunk 在哪个 Shard
- **职责**: 管理集群的配置信息。从 MongoDB 3.4 开始配置服务器必须部署为副本集CSRS以保证其高可用性。
![Sharded Cluster Architecture](https://docs.mongodb.com/manual/images/sharded-cluster-production-architecture.bakedsvg.svg)
---
## 分片键Shard Key
分片键是决定数据如何在各个分片之间分布的关键。选择一个好的分片键至关重要,它直接影响到分片集群的性能和效率。
### 分片键的选择策略
一个理想的分片键应该具备以下特征:
- **高基数 (High Cardinality)**: 分片键应该有大量可能的值,以便将数据均匀地分布到多个 Chunk 中。
- **低频率 (Low Frequency)**: 分片键的值应该被均匀地访问避免出现热点数据Hot Spot
- **非单调变化 (Non-Monotonic)**: 分片键的值不应随时间单调递增或递减,这会导致所有的写操作都集中在最后一个分片上。
### 分片策略
1. **范围分片 (Ranged Sharding)**
- **描述**: 根据分片键的范围将数据分成不同的块Chunk
- **优点**: 对于基于范围的查询(如 `find({ x: { $gt: 10, $lt: 20 } })`)非常高效,因为 `mongos` 可以直接将查询路由到存储该范围数据的分片。
- **缺点**: 如果分片键是单调变化的(如时间戳),容易导致写操作集中在单个分片上。
2. **哈希分片 (Hashed Sharding)**
- **描述**: 计算分片键的哈希值,并根据哈希值的范围来分片。
- **优点**: 能够将数据在各个分片之间均匀分布,保证了写操作的负载均衡。
- **缺点**: 对于范围查询不友好,因为相邻的分片键值可能被哈希到不同的分片上,导致查询需要广播到所有分片。
3. **标签感知分片 (Tag Aware Sharding)**
- **描述**: 允许管理员通过标签Tag将特定范围的数据块Chunk分配到特定的分片上。例如可以将美国用户的数据放在位于美国的服务器上以降低延迟。
---
## Chunks 和 Balancer
### 数据块 (Chunk)
- Chunk 是分片集合中一段连续的数据范围基于分片键。MongoDB 会试图保持 Chunk 的大小在一个可配置的范围内(默认为 64MB
- 当一个 Chunk 的大小超过配置值时,它会分裂成两个更小的 Chunk。
### 均衡器 (Balancer)
- Balancer 是一个后台进程,它负责在各个分片之间迁移 Chunk以确保数据在整个集群中均匀分布。
- 当某个分片的 Chunk 数量远多于其他分片时Balancer 会自动启动,并将一些 Chunk 从最拥挤的分片迁移到最空闲的分片。
- 均衡过程会消耗 I/O 和网络资源,可以在业务高峰期临时禁用 Balancer。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 分片集群,模拟电商平台的订单数据存储场景。该场景需要处理大量的订单数据,要求系统具备高可用性和水平扩展能力。通过实际操作来理解分片集群的部署、配置和管理过程。
### 实践细节和结果验证
```shell
# 1. 创建数据目录
mkdir -p /data/mongodb-sharding/{config1,config2,config3,shard1,shard2,mongos}
# 2. 启动配置服务器副本集 (CSRS)
# 启动三个配置服务器实例
mongod --configsvr --replSet configReplSet --port 27019 --dbpath /data/mongodb-sharding/config1 --fork --logpath /data/mongodb-sharding/config1.log
mongod --configsvr --replSet configReplSet --port 27020 --dbpath /data/mongodb-sharding/config2 --fork --logpath /data/mongodb-sharding/config2.log
mongod --configsvr --replSet configReplSet --port 27021 --dbpath /data/mongodb-sharding/config3 --fork --logpath /data/mongodb-sharding/config3.log
# 连接到配置服务器并初始化副本集
mongosh --port 27019
# 在 mongosh shell 中执行:
rs.initiate({
_id: "configReplSet",
configsvr: true,
members: [
{ _id: 0, host: "localhost:27019" },
{ _id: 1, host: "localhost:27020" },
{ _id: 2, host: "localhost:27021" }
]
})
# 验证配置服务器状态
rs.status()
# 预期结果:显示三个配置服务器节点,其中一个为 PRIMARY两个为 SECONDARY
# 3. 启动分片副本集
# 启动第一个分片
mongod --shardsvr --replSet shard1ReplSet --port 27022 --dbpath /data/mongodb-sharding/shard1 --fork --logpath /data/mongodb-sharding/shard1.log
# 启动第二个分片
mongod --shardsvr --replSet shard2ReplSet --port 27023 --dbpath /data/mongodb-sharding/shard2 --fork --logpath /data/mongodb-sharding/shard2.log
# 初始化分片副本集
mongosh --port 27022
# 在 mongosh shell 中执行:
rs.initiate({
_id: "shard1ReplSet",
members: [{ _id: 0, host: "localhost:27022" }]
})
mongosh --port 27023
# 在 mongosh shell 中执行:
rs.initiate({
_id: "shard2ReplSet",
members: [{ _id: 0, host: "localhost:27023" }]
})
# 4. 启动 mongos 查询路由
mongos --configdb configReplSet/localhost:27019,localhost:27020,localhost:27021 --port 27017 --fork --logpath /data/mongodb-sharding/mongos.log
# 5. 连接到 mongos 并添加分片
mongosh --port 27017
# 在 mongosh shell 中执行:
sh.addShard("shard1ReplSet/localhost:27022")
sh.addShard("shard2ReplSet/localhost:27023")
# 验证分片状态
sh.status()
# 预期结果:显示两个分片已成功添加到集群中
# 6. 为数据库和集合启用分片
# 启用数据库分片
sh.enableSharding("ecommerce")
# 为订单集合创建分片键并启用分片
sh.shardCollection("ecommerce.orders", { "customerId": 1 })
# 为订单集合创建分片键:哈希策略
# sh.shardCollection("ecommerce.orders", {"customerId": "hashed"})
# 设置新的 chunk 大小单位MB
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: { value: 1 } },
{ upsert: true }
)
# 7. 插入测试数据
use ecommerce
for (let i = 1; i <= 100000; i++) {
db.orders.insertOne({
customerId: Math.floor(Math.random() * 1000) + 1,
orderDate: new Date(2024, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
amount: Math.random() * 1000,
products: ["product" + (Math.floor(Math.random() * 100) + 1)]
})
}
# 8. 等待3min后观察数据分布和均衡过程
## partitioned: false 新版本已默认开启,可以忽略
sh.status()
# 预期结果:显示数据已分布到不同的分片上,可以看到 chunks 的分布情况
# 查看集合的分片信息
db.orders.getShardDistribution()
# 预期结果:显示每个分片上的文档数量和数据大小
sh.getShardedDataDistribution()
# 预期结果:显示每个分片上的数据库和集合的分布情况
# 9. 测试分片键查询性能
db.orders.find({customerId: 123}).explain("executionStats")
# 预期结果:查询只会路由到包含该 customerId 数据的特定分片
# 加速迁移
use config
db.settings.update(
{ "_id": "balancer" },
{ $set:
{
"_waitForDelete": false,
"_secondaryThrottle": false,
"writeConcern": { "w": "1" }
}
},
{ upsert: true }
)
```

View File

@@ -0,0 +1,188 @@
# 副本集Replica Set
为了提供高可用性和数据冗余MongoDB 使用副本集Replica Set的机制。本章节将详细介绍副本集的概念、架构、工作原理以及如何配置和管理一个副本集确保数据库服务能够抵御单点故障。
---
## 副本集概述
### 什么是副本集?
副本集是一组维护相同数据集的 `mongod` 进程。它由一个**主节点 (Primary)** 和多个**从节点 (Secondary)** 组成,共同保证了数据的冗余和高可用性。
- **主节点 (Primary)**: 接收所有的写操作。一个副本集在任何时候最多只能有一个主节点。
- **从节点 (Secondary)**: 从主节点异步复制数据。从节点可以接受读操作,从而分担主节点的读负载。
### 副本集的目标
1. **数据冗余 (Data Redundancy)**: 数据在多个服务器上有副本,防止因单台服务器硬件故障导致的数据丢失。
2. **高可用性 (High Availability)**: 当主节点发生故障时副本集会自动进行故障转移Failover从剩下的从节点中选举出一个新的主节点从而保证服务的持续可用。
3. **读写分离 (Read Scaling)**: 客户端可以将读请求路由到从节点,从而分散读负载,提高读取性能。
---
## 副本集架构与工作原理
### 副本集成员
一个典型的副本集包含以下成员:
- **主节点 (Primary)**: 唯一的写操作入口。
- **从节点 (Secondary)**: 复制主节点的数据,可以处理读请求。从节点也可以被配置为:
- **优先级为 0 的成员 (Priority 0 Member)**: 不能被选举为主节点,适合用于备份或离线分析。
- **隐藏成员 (Hidden Member)**: 对客户端不可见,不能处理读请求,通常用于备份。
- **延迟成员 (Delayed Member)**: 数据会比主节点延迟一段时间,可用于恢复误操作的数据。
- **仲裁者 (Arbiter)**: 只参与选举投票,不存储数据副本。它的存在是为了在成员数量为偶数时,打破平局,确保能够选举出主节点。
### 数据同步(复制)
- 主节点记录其所有的操作到一个特殊的 capped collection 中,称为 **oplog (operations log)**
- 从节点持续地从主节点的 oplog 中拉取新的操作,并在自己的数据集上应用这些操作,从而保持与主节点的数据同步。
- 这个过程是异步的,因此从节点的数据可能会有轻微的延迟。
### 选举过程Failover
当主节点在一定时间内(默认为 10 秒)无法与副本集中的其他成员通信时,会触发一次选举。
1. **触发选举**: 从节点发现主节点不可达。
2. **选举投票**: 剩下的健康成员会进行投票选举一个新的主节点。投票的依据包括成员的优先级、oplog 的新旧程度等。
3. **产生新的主节点**: 获得大多数票数(`(N/2) + 1`,其中 N 是副本集总成员数)的从节点会成为新的主节点。
4. **恢复同步**: 旧的主节点恢复后,会作为从节点重新加入副本集。
---
## 读写关注点Read and Write Concern
### 写关注点 (Write Concern)
写关注点决定了写操作在向客户端确认成功之前,需要被多少个副本集成员确认。
- **`w: 1` (默认)**: 写操作只需在主节点成功即可返回。
- **`w: "majority"`**: 写操作需要被大多数(`(N/2) + 1`)数据承载成员确认后才返回。这是推荐的设置,可以防止在故障转移期间发生数据回滚。
- **`j: true`**: 要求写操作在返回前写入到磁盘日志journal
### 读偏好 (Read Preference)
读偏好决定了客户端从哪个成员读取数据。
- **`primary` (默认)**: 只从主节点读取,保证数据最新。
- **`primaryPreferred`**: 优先从主节点读取,如果主节点不可用,则从从节点读取。
- **`secondary`**: 只从从节点读取,可以分担主节点负载,但可能读到稍有延迟的数据。
- **`secondaryPreferred`**: 优先从从节点读取,如果没有可用的从节点,则从主节点读取。
- **`nearest`**: 从网络延迟最低的成员读取,不关心是主节点还是从节点。
---
## 实践操作
### 需求描述
某电商公司的 MongoDB 数据库需要实现高可用性架构,要求:
1. 数据库服务 24/7 不间断运行
2. 单节点故障时自动故障转移
3. 支持读写分离以提升性能
4. 数据冗余备份防止数据丢失
我们需要搭建一个三成员副本集来满足这些需求,并验证其高可用性和读写分离功能。
### 实践细节和结果验证
```shell
# 1. 创建数据目录
mkdir -p /data/rs{1,2,3}
# 2. 启动三个 mongod 实例
# 实例 1 (Primary 候选)
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 实例 2 (Secondary 候选)
mongod --port 27028 --dbpath /data/rs2 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs2.log
# 实例 3 (Secondary 候选)
mongod --port 27029 --dbpath /data/rs3 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs3.log
# 3. 连接到第一个实例并初始化副本集
mongosh --port 27027
# 在 mongo shell 中执行以下命令
# 初始化副本集配置
config = {
_id: "myReplicaSet",
members: [
{ _id: 0, host: "localhost:27027", priority: 2 },
{ _id: 1, host: "localhost:27028", priority: 1 },
{ _id: 2, host: "localhost:27029", priority: 1 }
]
}
# 执行初始化
rs.initiate(config)
# 等待几秒后验证副本集状态
rs.status()
# 预期结果:显示一个 PRIMARY 节点和两个 SECONDARY 节点
# 4. 测试写操作(在主节点执行)
use testDB
db.products.insertOne({name: "iPhone 14", price: 999, stock: 100})
db.products.insertOne({name: "MacBook Pro", price: 2499, stock: 50})
# 验证写入成功
db.products.find()
# 预期结果:显示刚插入的两条记录
# 5. 测试读写分离
# 连接到副本集不设置读偏好,默认从主节点读取
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet"
# 数据来源一直为 27027
use testDB
myReplicaSet [primary] test> db.products.find().explain().serverInfo
# 连接到副本集并设置读偏好为从节点
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet&readPreference=secondary"
# readPreference 仅控制查询路由,不改变连接目标,但数据来源为 27028/27029
use testDB
db.products.find().explain().serverInfo
# 6. 模拟故障转移测试
# 在另一个终端中找到主节点进程并停止
ps aux | grep "mongod.*27027"
kill <主节点进程ID>
# 回到 mongo shell 观察选举过程
rs.status()
# 等待 10-30 秒后再次检查
rs.status()
# 预期结果:原来的一个从节点变成新的主节点
# 例如 localhost:27028 变成 PRIMARY 状态
# 7. 验证故障转移后的读写功能
# 在新主节点上执行写操作
mongosh --port 27028
use testDB
db.products.insertOne({name: "iPad Air", price: 599, stock: 75})
# 验证数据写入成功
db.products.find()
# 预期结果:显示包括新插入记录在内的所有数据
# 8. 恢复故障节点
# 重新启动之前停止的节点
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 验证节点重新加入副本集
rs.status()
# 预期结果localhost:27027 重新出现在成员列表中,状态为 SECONDARY因为 27027 节点优先级配置更高会主动出发选举并抢占主节点角色这是MongoDB副本集设计的正常行为
# 9. 验证数据同步
# 连接到恢复的节点
mongosh --port 27027
use testDB
db.getMongo().setReadPref("secondary")
db.products.find()
# 预期结果:显示所有数据,包括故障期间插入的记录,证明数据同步正常
# [扩展] 10. 性能测试 - 验证读写分离效果
```

View File

@@ -0,0 +1,161 @@
# 基础操作
本章节将引导大家学习 MongoDB 的基础操作,包括如何使用 MongoDB Shell、管理数据库和集合以及对文档进行核心的增删改查CRUD操作。掌握这些基础是进行更高级应用的前提。
---
## MongoDB Shell 使用
MongoDB Shell 是一个功能强大的交互式 JavaScript 接口,用于管理和操作 MongoDB 数据库。
- **连接数据库**:
打开终端,输入 `mongo``mongosh` 命令即可连接到本地默认的 MongoDB 实例 (mongodb://127.0.0.1:27017)。
```shell
mongosh "mongodb://<host>:<port>/<database>" -u <username> -p
```
- **基本命令**:
- `show dbs`: 显示所有数据库列表。
- `use <db_name>`: 切换到指定数据库,如果不存在则在首次插入数据时创建。
- `show collections`: 显示当前数据库中的所有集合。
- `db`: 显示当前所在的数据库。
- `db.stats()`: 显示当前数据库的状态信息。
- `exit` 或 `quit()`: 退出 Shell。
---
## 数据库操作
- **创建数据库**: 无需显式创建,当向一个不存在的数据库中的集合插入第一条数据时,该数据库会自动创建。
- **查看数据库**: `show dbs`
- **切换数据库**: `use myNewDB`
- **删除数据库**: 首先切换到要删除的数据库,然后执行 `db.dropDatabase()`。
---
## 集合操作
- **创建集合**:
- **隐式创建**: 当向一个不存在的集合插入第一条数据时,集合会自动创建。
- **显式创建**: 使用 `db.createCollection()` 方法,可以指定更多选项,如大小限制、验证规则等。
```javascript
db.createCollection("myCollection", { capped: true, size: 100000 })
```
- **查看集合**: `show collections`
- **删除集合**: `db.myCollection.drop()`
---
## 文档 CRUD 操作
CRUD 代表创建 (Create)、读取 (Read)、更新 (Update) 和删除 (Delete) 操作。
### 插入文档 (Create)
- **`insertOne()`**: 插入单个文档。
```javascript
db.inventory.insertOne({ item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } })
```
- **`insertMany()`**: 插入多个文档。
```javascript
db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
{ item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } }
])
```
### 查询文档 (Read)
- **`find()`**: 查询集合中所有匹配的文档。
```javascript
// 查询所有文档
db.inventory.find({})
// 查询 qty 大于 50 的文档
db.inventory.find({ qty: { $gt: 50 } })
```
- **`findOne()`**: 只返回匹配的第一个文档。
```javascript
db.inventory.findOne({ item: "journal" })
```
### 更新文档 (Update)
- **`updateOne()`**: 更新匹配的第一个文档。
```javascript
db.inventory.updateOne(
{ item: "journal" },
{ $set: { "size.uom": "in" }, $currentDate: { lastModified: true } }
)
```
- **`updateMany()`**: 更新所有匹配的文档。
```javascript
db.inventory.updateMany(
{ qty: { $gt: 50 } },
{ $set: { "size.uom": "in" }, $currentDate: { lastModified: true } }
)
```
- **`replaceOne()`**: 替换匹配的第一个文档。
### 删除文档 (Delete)
- **`deleteOne()`**: 删除匹配的第一个文档。
```javascript
db.inventory.deleteOne({ item: "journal" })
```
- **`deleteMany()`**: 删除所有匹配的文档。
```javascript
db.inventory.deleteMany({ qty: { $gt: 50 } })
```
---
## 实践操作
### 需求描述
1. **创建与切换**: 创建一个名为 `bookstore` 的数据库并切换过去。
2. **插入数据**: 在 `bookstore` 数据库中创建一个 `books` 集合,并批量插入至少 5 本书的数据,每本书包含 `title`, `author`, `published_year`, `genres` (数组), `stock` (库存) 字段。
3. **查询练习**:
- 查询所有库存量小于 10 本的书。
- 查询所有 `Science Fiction` 类型的书。
3. **更新练习**: 将指定一本书的库存量增加 5。
4. **删除练习**: 删除所有 `published_year` 在 1950 年之前的书。
5. **数据导入导出**: 使用 `mongoexport` 将 `books` 集合导出为 JSON 文件,然后使用 `mongoimport` 将其导入到一个新的集合 `books_backup` 中。
### 实践细节和结果验证
```javascript
// 1. 创建与切换数据库
use bookstore;
// 2. 插入数据
// 参考 data.js 文件中 Data for: MongoDB基础操作.md 下 books 集合的插入数据部分
// 3. 查询练习
// 查询所有库存量小于 10 本的书
db.books.find({ stock: { $lt: 10 } });
// 查询所有 Science Fiction 类型的书
db.books.find({ genres: "Science Fiction" });
// 4. 更新练习
// 将指定一本书的库存量增加 5
db.books.updateOne(
{ title: "Dune" },
{ $inc: { stock: 5 } }
);
// 5. 删除练习
// 删除所有 published_year 在 1950 年之前的书
db.books.deleteMany({ published_year: { $lt: 1950 } });
// 6. 数据导入导出
// 使用 mongoexport 将 books 集合导出为 JSON 文件
// mongoexport --db bookstore --collection books --out books.json --jsonArray
// 使用 mongoimport 将其导入到一个新的集合 books_backup 中
// mongoimport --db bookstore --collection books_backup --file books.json --jsonArray
```

View File

@@ -0,0 +1,113 @@
# 基础概念
本章节将介绍 NoSQL 数据库的基本概念,并深入探讨 MongoDB 的核心概念、架构原理,为后续的学习打下坚实的基础。
---
## NoSQL 数据库概述
NoSQLNot Only SQL泛指非关系型数据库它们在数据结构、可扩展性和事务模型上与传统的关系型数据库如 MySQL有显著不同。
### 关系型数据库 vs NoSQL 数据库
| 特性 | 关系型数据库 (RDBMS) | NoSQL 数据库 |
| :--- | :--- | :--- |
| **数据模型** | 基于表Table和行Row的结构化数据 | 多样化模型(文档、键值、列族、图) |
| **Schema** | 固定Schema-on-Write需预先定义表结构 | 动态Schema-on-Read数据结构灵活 |
| **扩展性** | 主要通过垂直扩展Scale-Up提升单机性能 | 主要通过水平扩展Scale-Out构建分布式集群 |
| **事务** | 遵循 ACID 原则,保证强一致性 | 通常遵循 BASE 原则,保证最终一致性 |
| **适用场景** | 事务性要求高、数据结构稳定的应用 | 大数据、高并发、需要快速迭代的应用 |
### NoSQL 数据库分类
NoSQL 数据库根据其数据模型的不同,主要分为以下几类:
1. **文档型数据库 (Document-Oriented)**
- **特点**:以文档(通常是 JSON 或 BSON 格式)为单位存储数据,结构灵活。
- **代表**MongoDB, CouchDB
2. **键值型数据库 (Key-Value)**
- **特点**:数据以简单的键值对形式存储,查询速度快。
- **代表**Redis, Memcached
3. **列族型数据库 (Column-Family)**
- **特点**:数据按列族存储,适合大规模数据聚合分析。
- **代表**Cassandra, HBase
4. **图形型数据库 (Graph)**
- **特点**:专注于节点和边的关系,适合社交网络、推荐系统等场景。
- **代表**Neo4j, ArangoDB
### MongoDB 在 NoSQL 生态中的定位
MongoDB 是文档型数据库的杰出代表,它凭借其灵活的数据模型、强大的查询语言和高可扩展性,在 NoSQL 生态中占据了重要地位。它特别适用于需要快速开发、数据结构多变、高并发读写的应用场景。
---
## 核心概念
理解 MongoDB 的核心概念是掌握其使用的第一步。
### 文档Document与 BSON 格式
- **文档 (Document)**:是 MongoDB 中数据的基本单元由一组键值对key-value组成类似于 JSON 对象。文档的结构是动态的,同一个集合中的文档可以有不同的字段。
```json
{
"_id": ObjectId("60c72b2f9b1d8b3b8c8b4567"),
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"tags": ["mongodb", "database", "nosql"]
}
```
- **BSON (Binary JSON)**MongoDB 在内部使用 BSON 格式存储文档。BSON 是 JSON 的二进制表示形式,它支持更多的数据类型(如日期、二进制数据),并优化了存储空间和查询性能。
### 集合Collection概念
- **集合 (Collection)**:是 MongoDB 文档的容器可以看作是关系型数据库中的表Table。但与表不同集合不需要预先定义结构schema-less
### 数据库Database结构
- **数据库 (Database)**:是集合的物理容器。一个 MongoDB 实例可以承载多个数据库,每个数据库都有自己独立的权限和文件。
### MongoDB 与关系型数据库术语对比
| MongoDB | 关系型数据库 (RDBMS) |
| :--- | :--- |
| Database | Database |
| Collection | Table |
| Document | Row (or Record) |
| Field | Column (or Attribute) |
| Index | Index |
| Replica Set | Master-Slave Replication |
| Sharding | Partitioning |
---
## 架构原理
了解 MongoDB 的底层架构有助于我们更好地进行性能调优和故障排查。
### 存储引擎WiredTiger
WiredTiger 是 MongoDB 默认的存储引擎,它提供了文档级别的并发控制、数据压缩和快照等高级功能,是 MongoDB 高性能的关键。
### 内存映射文件系统
MongoDB 使用内存映射文件Memory-Mapped Files来处理数据。它将磁盘上的数据文件映射到内存中使得 MongoDB 可以像访问内存一样访问数据,从而将数据管理委托给操作系统的虚拟内存管理器,简化了代码并提升了性能。
### 索引机制
为了提高查询效率MongoDB 支持在任意字段上创建索引。与关系型数据库类似MongoDB 的索引也采用 B-Tree 数据结构,能够极大地加速数据检索过程。
### 查询优化器
MongoDB 内置了一个查询优化器,它会分析查询请求,并从多个可能的查询计划中选择一个最优的执行计划,以确保查询的高效性。
---
## 文档学习
- 阅读 MongoDB 官方文档中关于“核心概念”的部分。

View File

@@ -0,0 +1,215 @@
# MongoDB 备份与恢复
制定可靠的备份和恢复策略是数据库管理中至关重要的一环,它可以帮助在发生数据损坏、误操作或灾难性故障时恢复数据。本章节将介绍 MongoDB 常用的备份方法、恢复流程以及灾难恢复的最佳实践。
---
## 备份策略
选择哪种备份方法取决于具体需求,如恢复时间目标 (RTO)、恢复点目标 (RPO)、数据库规模以及部署架构(独立实例、副本集、分片集群)。
### 备份方法
1. **`mongodump``mongorestore`**
- **描述**: MongoDB 提供的官方命令行工具,用于创建数据库的 BSON 文件备份,并可以从这些文件恢复数据。
- **优点**: 简单易用,适合小型数据集或对备份窗口要求不高的场景。
- **缺点**: 备份期间可能会影响数据库性能。对于大型数据集,备份和恢复过程可能非常耗时。在恢复 `mongodump` 的备份时,它会重建索引,这会增加恢复时间。
2. **文件系统快照 (Filesystem Snapshots)**
- **描述**: 利用文件系统(如 LVM或云存储如 AWS EBS的快照功能对 MongoDB 的数据文件进行即时备份。
- **优点**: 备份速度快,对数据库性能影响小。恢复速度也很快,因为数据和索引都无需重建。
- **缺点**: 必须确保在创建快照时,数据处于一致性状态。这通常需要开启 journaling 并确保在快照前执行 `fsync` 和 lock 操作。
3. **MongoDB Cloud Manager / Ops Manager**
- **描述**: MongoDB 提供的企业级管理工具,提供持续的、时间点恢复的备份服务。
- **优点**: 提供图形化界面,管理方便。支持副本集和分片集群的持续备份和时间点恢复,可以恢复到任意时刻。
- **缺点**: 是商业服务,需要额外成本。
---
## 使用 `mongodump` 和 `mongorestore`
### `mongodump`
`mongodump` 可以导出整个数据库、特定数据库或特定集合。
```shell
# 备份整个 MongoDB 实例
mongodump --out /data/backup/`date +%F`
# 备份指定的数据库
mongodump --db myAppDB --out /data/backup/myAppDB
# 备份指定的集合
mongodump --db myAppDB --collection users --out /data/backup/users_collection
# 备份远程数据库并启用压缩
mongodump --host mongodb.example.com --port 27017 --username myUser --password myPass --authenticationDatabase admin --db myAppDB --gzip --out /data/backup/myAppDB.gz
```
### `mongorestore`
`mongorestore` 用于将 `mongodump` 创建的备份恢复到数据库中。
```shell
# 恢复整个实例的备份
mongorestore /data/backup/2023-10-27/
# 恢复指定的数据库备份
# mongorestore 会将数据恢复到备份时同名的数据库中
mongorestore --db myAppDB /data/backup/myAppDB/
# 恢复并删除现有数据
# 使用 --drop 选项可以在恢复前删除目标集合
mongorestore --db myAppDB --collection users --drop /data/backup/users_collection/users.bson
```
---
## 副本集和分片集群的备份
### 备份副本集
- **使用 `mongodump`**: 建议连接到一个从节点进行备份,以减少对主节点的影响。可以使用 `--oplog` 选项来创建一个包含 oplog 条目的时间点快照,这对于在恢复后与其他副本集成员同步非常重要。
```bash
mongodump --host secondary.example.com --oplog --out /data/backup/repl_set_backup
```
- **使用文件系统快照**: 可以在一个被停止(或锁定)的从节点上进行,以保证数据一致性。
### 备份分片集群
备份分片集群要复杂得多,因为必须保证整个集群的数据是一致的。
- **核心挑战**: 必须在同一时刻捕获所有分片和配置服务器的数据快照。
- **推荐方法**: **强烈建议使用 MongoDB Cloud Manager 或 Ops Manager** 来处理分片集群的备份,因为它们专门设计用于处理这种复杂性。
- **手动备份(不推荐,风险高)**:
1. 禁用 Balancer。
2. 对配置服务器进行 `mongodump`。
3. 对每个分片副本集进行 `mongodump`。
4. 重新启用 Balancer。
恢复过程同样复杂且容易出错。
---
## 恢复策略与灾难恢复
### 恢复误操作的数据
- **使用延迟从节点**: 如果配置了一个延迟从节点,可以停止它的复制,从其数据文件中恢复误删或误改的数据。
- **使用时间点恢复**: 如果使用 Cloud Manager / Ops Manager可以将数据库恢复到误操作发生前的任意时间点。
### 灾难恢复 (Disaster Recovery)
灾难恢复计划旨在应对整个数据中心或区域级别的故障。
- **异地备份**: 将备份数据存储在与生产数据中心物理位置不同的地方。
- **多数据中心部署**: 将副本集的成员分布在不同的地理位置。例如,一个主节点和从节点在主数据中心,另一个从节点在灾备数据中心。当主数据中心发生故障时,可以手动或自动将灾备中心的从节点提升为新的主节点。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 备份与恢复实践环境,掌握不同场景下的备份策略和恢复操作。通过实际操作理解备份工具的使用方法、副本集备份的最佳实践,以及制定符合业务需求的备份恢复策略。
### 实践细节和结果验证
```shell
# 1. 准备测试数据
# 创建测试数据库和集合
use myAppDB
db.users.insertMany([
{name: "Alice", age: 25, email: "alice@example.com"},
{name: "Bob", age: 30, email: "bob@example.com"},
{name: "Charlie", age: 35, email: "charlie@example.com"}
])
db.orders.insertMany([
{orderId: "ORD001", userId: "Alice", amount: 100.50, status: "completed"},
{orderId: "ORD002", userId: "Bob", amount: 250.75, status: "pending"},
{orderId: "ORD003", userId: "Charlie", amount: 89.99, status: "completed"}
])
# 验证数据插入
db.users.countDocuments() # 预期结果: 3
db.orders.countDocuments() # 预期结果: 3
# 2. 使用 mongodump 备份数据库
# 创建备份目录
mkdir -pv /data/mongodb/backup
# 备份整个 myAppDB 数据库
mongodump --db myAppDB --out /data/mongodb/backup/$(date +%F)/dbs/
# 验证备份文件
ls -la /data/mongodb/backup/$(date +%F)/dbs/
# 预期结果: 应该看到 users.bson, users.metadata.json, orders.bson, orders.metadata.json 等文件
# 备份指定集合(带压缩)
mongodump --db myAppDB --collection users --gzip --out /data/mongodb/backup/$(date +%F)/collections/
# 验证压缩备份
ls -la /data/mongodb/backup/$(date +%F)/users_backup/myAppDB/
# 预期结果: 应该看到 users.bson.gz 和 users.metadata.json.gz 文件
# 3. 模拟数据丢失并恢复
# 删除 users 集合模拟数据丢失
use myAppDB
db.users.drop()
# 验证数据已删除
db.users.countDocuments() # 预期结果: 0
show collections # 预期结果: 只显示 orders 集合
# 使用 mongorestore 恢复 users 集合
gzip /data/mongodb/backup/$(date +%F)/collections/myAppDB/users.bson.gz -d /data/mongodb/backup/$(date +%F)/collections/myAppDB/
mongorestore --db myAppDB --collection users /data/mongodb/backup/$(date +%F)/collections/myAppDB/users.bson
# 验证数据恢复
db.users.countDocuments() # 预期结果: 3
db.users.find().pretty() # 预期结果: 显示所有用户数据
# 4. 完整数据库恢复测试
# 删除整个数据库
use myAppDB
db.dropDatabase()
# 验证数据库已删除
show dbs # 预期结果: myAppDB 不在列表中
# 恢复整个数据库
mongorestore /data/mongodb/backup/$(date +%F)/dbs/myAppDB/
# 验证数据库恢复
use myAppDB
show collections # 预期结果: 显示 users 和 orders 集合
db.users.countDocuments() # 预期结果: 3
db.orders.countDocuments() # 预期结果: 3
# 5. 副本集备份实践(如果已配置副本集)
# 连接到副本集的从节点进行备份
mongodump --host secondary.example.com:27017 --oplog --out /data/backup/replica_backup
# 验证副本集备份
ls -la /data/backup/replica_backup/
# 预期结果: 应该看到各个数据库目录和 oplog.bson 文件
# 6.[扩展] 制定生产环境备份策略
# 电商订单数据库备份策略设计
# 业务需求分析:
# - RTO (恢复时间目标): 4小时
# - RPO (恢复点目标): 1小时
# - 数据重要性: 订单数据极其重要,用户数据重要
# - 业务特点: 24/7运行高并发读写
# 推荐备份策略:
# 1. 每日全量备份 (使用文件系统快照)
# 2. 每小时增量备份 (使用 oplog)
# 3. 异地备份存储
# 4. 副本集跨数据中心部署
```

View File

@@ -0,0 +1,273 @@
# 安全管理
确保数据库的安全是任何应用都必须考虑的关键问题。MongoDB 提供了丰富的安全特性,包括认证、授权、加密等,以保护数据免受未经授权的访问。本章节将详细介绍如何配置和管理 MongoDB 的各项安全功能。
---
## 安全概述
MongoDB 的安全模型主要围绕以下几个核心概念构建:
- **认证 (Authentication)**: 验证用户身份,确认“你是谁”。
- **授权 (Authorization)**: 控制经过认证的用户可以执行哪些操作,确认“你能做什么”。
- **加密 (Encryption)**: 保护数据在传输过程TLS/SSL和静态存储时Encryption at Rest的安全。
- **审计 (Auditing)**: 记录数据库上发生的活动,以便进行安全分析和合规性检查。
默认情况下MongoDB 的安全特性是**未启用**的。必须显式地配置和启用它们。
---
## 认证机制 (Authentication)
启用认证是保护数据库的第一步。当认证启用后,所有客户端和数据库节点之间的连接都必须提供有效的凭据。
### 启用认证
`mongod.conf` 配置文件中或通过命令行参数启用认证:
```yaml
# mongod.conf
security:
authorization: enabled
```
或者
```bash
mongod --auth
```
### 认证方法
MongoDB 支持多种认证机制,最常用的是 **SCRAM (Salted Challenge Response Authentication Mechanism)**
- **SCRAM-SHA-1**: 默认机制。
- **SCRAM-SHA-256**: 更强的加密算法,建议在 MongoDB 4.0 及以上版本中使用。
### 创建管理员用户
在启用认证之前,必须先创建一个具有 `userAdminAnyDatabase` 角色的用户管理员。这个用户将用于创建和管理其他用户。
1. **以无认证模式启动 `mongod`**
2. **连接到 `admin` 数据库并创建用户**:
```javascript
use admin
db.createUser({
user: "myUserAdmin",
pwd: passwordPrompt(), // or a plain-text password
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})
```
3. **重启 `mongod` 并启用认证**。
---
## 授权与基于角色的访问控制 (RBAC)
MongoDB 使用基于角色的访问控制Role-Based Access Control, RBAC来管理用户权限。权限被定义为**角色 (Role)**,然后将角色分配给用户。
### 内置角色 (Built-In Roles)
MongoDB 提供了一系列预定义的角色,涵盖了常见的管理和操作需求。
- **数据库用户角色**: `read`, `readWrite`
- **数据库管理员角色**: `dbAdmin`, `dbOwner`, `userAdmin`
- **集群管理员角色**: `clusterAdmin`, `clusterManager`, `hostManager`
- **备份与恢复角色**: `backup`, `restore`
- **所有数据库角色**: `readAnyDatabase`, `readWriteAnyDatabase`, `userAdminAnyDatabase`, `dbAdminAnyDatabase`
- **超级用户角色**: `root` (拥有所有权限)
### 自定义角色 (Custom Roles)
如果内置角色无法满足精细化权限控制需求,可以创建自定义角色。
```javascript
use myAppDB
db.createRole({
role: "salesDataViewer",
privileges: [
{ resource: { db: "myAppDB", collection: "sales" }, actions: ["find"] }
],
roles: []
})
```
### 管理用户和角色
- `db.createUser()`: 创建用户。
- `db.updateUser()`: 更新用户信息(如密码、角色)。
- `db.dropUser()`: 删除用户。
- `db.createRole()`: 创建角色。
- `db.grantRolesToUser()`: 为用户授予角色。
- `db.revokeRolesFromUser()`: 撤销用户的角色。
---
## 网络加密 (TLS/SSL)
为了保护数据在网络传输过程中的安全,防止窃听和中间人攻击,应该为 MongoDB 部署启用 TLS/SSL 加密。
### 配置 TLS/SSL
1. **获取 TLS/SSL 证书**: 可以使用自签名证书(用于内部测试)或从证书颁发机构 (CA) 获取证书。
2. **配置 `mongod` 和 `mongos`**: 在配置文件中指定证书文件、私钥文件和 CA 文件。
```yaml
net:
tls:
mode: requireTLS
certificateKeyFile: /path/to/mongodb.pem
CAFile: /path/to/ca.pem
```
3. **客户端连接**: 客户端在连接时也需要指定 TLS/SSL 选项。
```shell
mongo --ssl --sslCAFile /path/to/ca.pem --sslPEMKeyFile /path/to/client.pem ...
```
---
## 静态数据加密 (Encryption at Rest)
对于高度敏感的数据,除了网络加密,还应该考虑对存储在磁盘上的数据文件进行加密。
- **WiredTiger 的原生加密**: MongoDB Enterprise 版本支持使用 WiredTiger 存储引擎的原生加密功能。它使用本地密钥管理器或第三方密钥管理服务(如 KMIP来管理加密密钥。
- **文件系统/磁盘加密**: 也可以在操作系统层面或通过云服务商提供的功能(如 AWS KMS, Azure Key Vault对存储设备进行加密。
---
## 实践操作
### 需求描述
构建一个完整的MongoDB安全管理环境模拟企业级应用的安全需求。该场景需要配置认证机制、创建不同权限的用户角色并验证安全策略的有效性。通过实际操作来理解MongoDB安全管理的核心概念和最佳实践。
### 实践细节和结果验证
```shell
# 1. 启用认证并创建管理员用户
# 首先以无认证模式启动MongoDB
mongod --dbpath /data/mongodb/ins11 --port 27117 --fork --logpath /data/mongodb/logs/ins11.log
# 连接到MongoDB并创建管理员用户
mongosh --port 27117
# 在mongosh中执行
use admin
db.createUser({
user: "admin",
pwd: "ealgeslab123",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})
# 验证管理员用户创建成功
db.getUsers()
# 预期结果显示创建的admin用户信息
# 2. 重启MongoDB并启用认证
# 停止MongoDB服务
db.adminCommand("shutdown")
# 以认证模式重新启动
mongod --auth --dbpath /data/mongodb/ins11 --port 27117 --fork --logpath /data/mongodb/logs/ins11.log
# 使用管理员账户登录
mongosh --port 27117 -u "admin" -p "ealgeslab123" --authenticationDatabase "admin"
# 3. 创建业务数据库和用户
# 创建应用数据库
use ecommerce
# 创建具有读写权限的业务用户
db.createUser({
user: "appUser",
pwd: "appPassword123",
roles: [{ role: "readWrite", db: "ecommerce" }]
})
# 验证业务用户创建成功
db.getUsers()
# 预期结果显示appUser用户信息
# 4. 创建自定义角色
# 创建只允许查询和更新特定集合的自定义角色
db.createRole({
role: "productManager",
privileges: [
{
resource: { db: "ecommerce", collection: "products" },
actions: ["find", "update"]
}
],
roles: []
})
# 创建使用自定义角色的用户
db.createUser({
user: "productAdmin",
pwd: "productPassword123",
roles: [{ role: "productManager", db: "ecommerce" }]
})
# 验证自定义角色和用户
db.getRoles({ showPrivileges: true })
db.getUsers()
# 预期结果显示productManager角色和productAdmin用户
# 5. 测试用户权限
# 使用业务用户连接
mongosh --port 27117 -u "appUser" -p "appPassword123" --authenticationDatabase "ecommerce"
# 测试读写权限
use ecommerce
db.products.insertOne({ name: "Laptop", price: 999, category: "Electronics" })
db.products.find()
db.products.updateOne({ name: "Laptop" }, { $set: { price: 899 } })
# 预期结果:所有操作成功执行
# 6. 测试自定义角色权限
# 使用自定义角色用户连接
mongosh --port 27017 -u "productAdmin" -p "productPassword123" --authenticationDatabase "ecommerce"
use ecommerce
# 测试允许的操作
db.products.find()
db.products.updateOne({ name: "Laptop" }, { $set: { description: "High-performance laptop" } })
# 预期结果:查询和更新操作成功
# 测试禁止的操作
db.products.insertOne({ name: "Mouse", price: 25 })
# 预期结果:操作失败,提示权限不足
db.products.deleteOne({ name: "Laptop" })
# 预期结果:操作失败,提示权限不足
# 8. 查看用户和角色信息
# 查看所有用户
use admin
db.system.users.find().pretty()
# 查看所有角色
db.system.roles.find().pretty()
# 查看当前用户权限
db.runCommand({ connectionStatus: 1 })
# 预期结果:显示当前连接的用户信息和权限
# 9. 权限管理操作
use ecommerce
# 为用户添加新角色
db.grantRolesToUser("appUser", [{ role: "dbAdmin", db: "ecommerce" }])
# 撤销用户角色
db.revokeRolesFromUser("appUser", [{ role: "dbAdmin", db: "ecommerce" }])
# 修改用户密码
db.updateUser("appUser", { pwd: "newPassword123" })
# 验证权限变更
db.getUser("appUser")
# 预期结果:显示用户的最新角色信息
```

View File

@@ -0,0 +1,300 @@
# MongoDB 性能监控与调优
为了确保 MongoDB 数据库持续、稳定、高效地运行,必须对其进行有效的性能监控和及时的调优。本章节将介绍 MongoDB 的关键性能指标、常用的监控工具以及针对常见性能问题的调优策略。
---
## 关键性能指标
监控 MongoDB 时,应重点关注以下几类指标:
### 操作计数器 (Operation Counters)
- **`opcounters`**: 显示自 `mongod` 启动以来执行的数据库操作insert, query, update, delete, getmore, command的总数。通过观察其增长率可以了解数据库的负载情况。
### 锁 (Locks)
- **`globalLock`**: 反映全局锁的使用情况。在 WiredTiger 存储引擎中,全局锁的使用已大大减少,但仍需关注。
- **`locks`**: 提供数据库、集合等更细粒度锁的信息。长时间的锁等待(`timeAcquiringMicros`)可能表示存在锁竞争,需要优化查询或索引。
### 网络 (Network)
- **`network.bytesIn` / `network.bytesOut`**: 进出数据库的网络流量。
- **`network.numRequests`**: 接收到的请求总数。
- 监控网络指标有助于发现网络瓶颈或异常的客户端行为。
### 内存 (Memory)
- **`mem.resident`**: 进程占用的物理内存RAM大小。
- **`mem.virtual`**: 进程使用的虚拟内存大小。
- **`mem.mapped`**: 内存映射文件的大小。
- 监控内存使用情况,特别是 WiredTiger 的内部缓存(`wiredTiger.cache`),对于确保性能至关重要。
### Oplog
- **Oplog Window**: Oplog 中记录的操作所覆盖的时间范围。如果 oplog window 太小从节点可能会因为跟不上主节点的更新速度而掉队stale
### 慢查询 (Slow Queries)
- **`system.profile` 集合**: 当开启数据库分析profiling执行时间超过阈值的查询会被记录到该集合中。这是识别和优化慢查询的主要工具。
---
## 监控工具
### `mongostat`
- **描述**: 一个命令行工具,可以实时地、逐秒地显示 MongoDB 实例的主要性能指标,类似于 Linux 的 `vmstat`
- **用途**: 快速了解数据库当前的操作负载和性能状况。
```bash
mongostat
```
### `mongotop`
- **描述**: 一个命令行工具,用于跟踪 MongoDB 实例花费时间最多的读写操作。
- **用途**: 快速定位哪些集合是当前系统的性能热点。
```bash
mongotop 10 # 每 10 秒刷新一次
```
### `db.serverStatus()`
- **描述**: 一个数据库命令,返回一个包含大量服务器状态信息的文档。
- **用途**: 获取详细的、全面的性能指标,是大多数监控系统的主要数据来源。
```javascript
db.serverStatus()
```
### MongoDB Cloud Manager / Ops Manager
- **描述**: 提供了一个功能强大的图形化监控界面,可以收集、可视化并告警各种性能指标。
- **用途**: 长期、系统化的性能监控和趋势分析。
---
## 性能调优策略
### 索引优化
- **识别缺失的索引**: 通过分析 `system.profile` 集合中的慢查询日志,找出那些因为缺少合适索引而导致全集合扫描(`COLLSCAN`)的查询。
- **评估现有索引**: 使用 `$indexStats` 聚合阶段检查索引的使用频率。对于很少使用或从不使用的索引,应考虑删除以减少写操作的开销和存储空间。
- **创建复合索引**: 根据 ESR 法则设计高效的复合索引,使其能够覆盖多种查询模式。
### 查询优化
- **使用投影 (Projection)**: 只返回查询所需的字段,减少网络传输量和客户端处理数据的负担。
- **避免使用 `$where` 和 `$function`**: 这类操作无法使用索引,并且会在每个文档上执行 JavaScript性能极差。
- **优化正则表达式**: 尽量使用前缀表达式(如 `/^prefix/`),这样可以利用索引。避免使用不区分大小写的正则表达式,因为它无法有效利用索引。
### Schema 设计调优
- **遵循数据建模模式**: 根据应用的读写模式选择合适的建模策略(嵌入 vs. 引用),可以从根本上提升性能。
- **避免大文档和无界数组**: 过大的文档会增加内存使用和网络传输,无限制增长的数组会给更新操作带来性能问题。
### 硬件和拓扑结构调优
- **内存**: 确保 WiredTiger 的缓存大小(默认为 `(RAM - 1GB) / 2`足够容纳工作集Working Set即最常访问的数据和索引。
- **磁盘**: 使用 SSD 可以显著提升 I/O 性能,特别是对于写密集型应用。
- **网络**: 确保数据库服务器之间的网络延迟尽可能低,特别是在分片集群和地理分布的副本集中。
- **读写分离**: 在读密集型应用中,通过将读请求路由到从节点来扩展读取能力。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 性能监控与调优实践环境,掌握性能指标监控、慢查询分析、索引优化等核心技能。通过实际操作理解如何识别性能瓶颈、分析查询执行计划、创建高效索引,以及使用各种监控工具来持续优化数据库性能。
### 实践细节和结果验证
```shell
# 1. 准备测试数据和环境
# 创建性能测试数据库
use performanceTestDB
# 创建大量测试数据
for (let i = 0; i < 100000; i++) {
db.products.insertOne({
productId: "PROD" + i.toString().padStart(6, '0'),
name: "Product " + i,
category: ["electronics", "books", "clothing", "home"][i % 4],
price: Math.floor(Math.random() * 1000) + 10,
rating: Math.floor(Math.random() * 5) + 1,
inStock: Math.random() > 0.3,
tags: ["popular", "new", "sale", "featured"].slice(0, Math.floor(Math.random() * 3) + 1),
createdAt: new Date(Date.now() - Math.floor(Math.random() * 365 * 24 * 60 * 60 * 1000))
});
}
# 验证数据插入
db.products.countDocuments() # 预期结果: 100000
# 2. 开启数据库性能分析 (Profiling)
# 设置慢查询阈值为100ms记录所有慢查询
db.setProfilingLevel(1, { slowms: 100 })
# 验证profiling设置
db.getProfilingStatus()
# 预期结果: { "was" : 1, "slowms" : 100, "sampleRate" : 1.0 }
# 3. 生成慢查询进行性能分析
# 执行没有索引的复杂查询(故意制造慢查询)
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 },
inStock: true
}).sort({ createdAt: -1 }).limit(10)
# 执行正则表达式查询(通常较慢)
db.products.find({ name: /Product 1.*/ })
# 执行范围查询
db.products.find({
price: { $gte: 100, $lte: 500 },
createdAt: { $gte: new Date("2023-01-01") }
})
# 4. 分析慢查询日志
# 查看最近的慢查询记录
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()
# 预期结果: 显示最近5条慢查询记录包含执行时间、查询语句等信息
# 查看特定类型的慢查询
db.system.profile.find({
"command.find": "products",
"millis": { $gte: 100 }
}).sort({ ts: -1 })
# 统计慢查询数量
db.system.profile.countDocuments({ "millis": { $gte: 100 } })
# 预期结果: 显示慢查询总数
# 5. 使用 explain() 分析查询执行计划
# 分析复杂查询的执行计划
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 }
}).explain("executionStats")
# 预期结果: 显示详细的执行统计,包括扫描的文档数、执行时间等
# 查看查询是否使用了索引
db.products.find({ category: "electronics" }).explain("queryPlanner")
# 预期结果: winningPlan.stage 应该显示 "COLLSCAN"(全集合扫描)
# 6. 创建索引优化查询性能
# 为常用查询字段创建单字段索引
db.products.createIndex({ category: 1 })
db.products.createIndex({ price: 1 })
db.products.createIndex({ rating: 1 })
db.products.createIndex({ createdAt: -1 })
# 创建复合索引遵循ESR法则Equality, Sort, Range
db.products.createIndex({
category: 1,
createdAt: -1,
price: 1
})
# 创建文本索引用于搜索
db.products.createIndex({ name: "text", tags: "text" })
# 验证索引创建
db.products.getIndexes()
# 预期结果: 显示所有已创建的索引
# 7. 验证索引优化效果
# 重新执行之前的慢查询,观察性能提升
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 }
}).sort({ createdAt: -1 }).explain("executionStats")
# 预期结果: 应该显示 "IXSCAN"(索引扫描),执行时间显著减少
# 比较优化前后的查询性能
var startTime = new Date();
db.products.find({ category: "electronics", price: { $gte: 500 } }).toArray();
var endTime = new Date();
print("查询执行时间: " + (endTime - startTime) + "ms");
# 预期结果: 执行时间应该显著减少
# 8. 使用监控工具进行性能监控
# 在终端中使用 mongostat 监控实时性能
# mongostat --host localhost:27017
# 预期结果: 显示实时的操作统计、内存使用、网络流量等
# 使用 mongotop 监控集合级别的读写活动
# mongotop 10
# 预期结果: 每10秒显示各集合的读写时间统计
# 9. 服务器状态监控
# 获取详细的服务器状态信息
db.serverStatus()
# 预期结果: 返回包含操作计数器、锁信息、内存使用、网络统计等的详细报告
# 监控特定的性能指标
db.serverStatus().opcounters
# 预期结果: 显示各种操作insert、query、update等的计数
db.serverStatus().wiredTiger.cache
# 预期结果: 显示WiredTiger缓存的使用情况
db.serverStatus().locks
# 预期结果: 显示锁的使用统计
# 10. 索引使用情况分析
# 查看索引使用统计
db.products.aggregate([
{ $indexStats: {} }
])
# 预期结果: 显示每个索引的使用次数和访问模式
# 识别未使用的索引
db.products.aggregate([
{ $indexStats: {} },
{ $match: { "accesses.ops": 0 } }
])
# 预期结果: 显示从未被使用的索引(可考虑删除)
# 11. 查询优化最佳实践验证
# 使用投影减少网络传输
var startTime = new Date();
db.products.find(
{ category: "electronics" },
{ name: 1, price: 1, _id: 0 } # 只返回需要的字段
).toArray();
var endTime = new Date();
print("投影查询执行时间: " + (endTime - startTime) + "ms");
# 对比不使用投影的查询时间
var startTime = new Date();
db.products.find({ category: "electronics" }).toArray();
var endTime = new Date();
print("完整文档查询执行时间: " + (endTime - startTime) + "ms");
# 预期结果: 使用投影的查询应该更快
# 12. 清理和总结
# 关闭profiling
db.setProfilingLevel(0)
# 查看profiling总结
db.system.profile.aggregate([
{
$group: {
_id: "$command.find",
avgDuration: { $avg: "$millis" },
maxDuration: { $max: "$millis" },
count: { $sum: 1 }
}
},
{ $sort: { avgDuration: -1 } }
])
# 预期结果: 显示各集合查询的平均执行时间统计
```

View File

@@ -0,0 +1,299 @@
# 数据建模
与关系型数据库不同MongoDB 灵活的文档模型为数据建模提供了更多的选择。本章节将介绍 MongoDB 数据建模的核心思想、常见模式以及如何根据应用需求选择合适的模型,以实现最佳的性能和可扩展性。
---
## 数据建模核心思想
### 嵌入Embedding vs. 引用Referencing
这是 MongoDB 数据建模中最核心的决策点。
- **嵌入 (Embedding / Denormalization)**
- **描述**: 将相关的数据直接嵌入到主文档中,形成一个嵌套的文档结构。
- **优点**:
- **性能好**: 只需一次数据库查询即可获取所有相关数据,减少了读操作的次数。
- **数据原子性**: 对单个文档的更新是原子操作。
- **缺点**:
- **文档体积大**: 可能导致文档超过 16MB 的大小限制。
- **数据冗余**: 如果嵌入的数据在多处被使用,更新时需要修改所有包含它的文档。
- **适用场景**: “一对一”或“一对多”关系,且“多”的一方数据不经常变动,或者总是和“一”的一方一起被查询。
- **引用 (Referencing / Normalization)**
- **描述**: 将数据存储在不同的集合中,通过在文档中存储对另一个文档的引用(通常是 `_id`)来建立关系。
- **优点**:
- **数据一致性**: 更新数据时只需修改一处。
- **文档体积小**: 避免了数据冗余。
- **缺点**:
- **查询性能较低**: 需要多次查询(或使用 `$lookup`)来获取关联数据。
- **适用场景**: “多对多”关系,或者“一对多”关系中“多”的一方数据量巨大、经常变动或经常被独立查询。
### 决策依据
选择嵌入还是引用,主要取决于应用的**数据访问模式**
- **读多写少**: 优先考虑嵌入,优化读取性能。
- **写操作频繁**: 优先考虑引用,避免更新大量冗余数据。
- **数据一致性要求高**: 优先考虑引用。
- **数据关联性强,总是一起访问**: 优先考虑嵌入。
---
## 常见数据建模模式
MongoDB 社区总结了一些行之有效的数据建模模式,可以作为设计的参考。
### 属性模式 (Attribute Pattern)
- **问题**: 当文档有大量字段,但大部分查询只关心其中一小部分时。
- **解决方案**: 将不常用或具有相似特征的字段分组到一个子文档中。
- **示例**: 一个产品文档,将详细规格参数放到 `specs` 子文档中。
```json
{
"product_id": "123",
"name": "Laptop",
"specs": {
"cpu": "Intel i7",
"ram_gb": 16,
"storage_gb": 512
}
}
```
### 扩展引用模式 (Extended Reference Pattern)
- **问题**: 在引用模型中,为了获取被引用文档的某个常用字段,需要执行额外的查询。
- **解决方案**: 在引用文档中,除了存储 `_id` 外,还冗余存储一些经常需要一起显示的字段。
- **示例**: 在文章(`posts`)集合中,除了存储作者的 `author_id`,还冗余存储 `author_name`。
```json
// posts collection
{
"title": "My First Post",
"author_id": "xyz",
"author_name": "John Doe" // Extended Reference
}
```
### 子集模式 (Subset Pattern)
- **问题**: 一个文档中有一个巨大的数组(如评论、日志),导致文档过大,且大部分时间只需要访问数组的最新一部分。
- **解决方案**: 将数组的一个子集(如最新的 10 条评论)与主文档存储在一起,完整的数组存储在另一个集合中。
- **示例**: 产品文档中存储最新的 5 条评论,所有评论存储在 `reviews` 集合。
```json
// products collection
{
"product_id": "abc",
"name": "Super Widget",
"reviews_subset": [
{ "review_id": 1, "text": "Great!" },
{ "review_id": 2, "text": "Awesome!" }
]
}
```
### 计算模式 (Computed Pattern)
- **问题**: 需要频繁计算某些值(如总数、平均值),每次读取时计算会消耗大量资源。
- **解决方案**: 在写操作时预先计算好这些值,并将其存储在文档中。当数据更新时,同步更新计算结果。
- **示例**: 在用户文档中存储其发布的帖子总数 `post_count`。
```json
{
"user_id": "user123",
"name": "Jane Doe",
"post_count": 42 // Computed value
}
```
---
## Schema 验证
虽然 MongoDB 是 schema-less 的,但在应用层面保持数据结构的一致性非常重要。从 MongoDB 3.2 开始,引入了 Schema 验证功能。
- **作用**: 可以在集合级别定义文档必须满足的结构规则(如字段类型、必需字段、范围等)。
- **好处**: 确保写入数据库的数据符合预期格式,提高数据质量。
```javascript
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "major", "gpa"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
gpa: {
bsonType: ["double"],
minimum: 0,
maximum: 4.0,
description: "must be a double in [0, 4] and is required"
}
}
}
}
})
```
---
## 实践环节
### 需求描述
为一个博客系统设计数据模型,包含以下实体:`用户 (User)`、`文章 (Post)`、`评论 (Comment)` 和 `标签 (Tag)`。
1. **关系分析**: 分析这些实体之间的关系(一对一、一对多、多对多)。
2. **模型设计**: 讨论并设计至少两种不同的数据模型方案(例如,一种偏向嵌入,一种偏向引用)。
3. **优劣对比**: 对比不同方案的优缺点,并说明它们分别适用于哪些场景。
4. **Schema 定义**: 选择的最佳方案中的 `posts` 集合编写一个 Schema 验证规则。
### 实践细节和结果验证
```javascript
// 1. 关系分析与模型设计
// 方案一:偏向嵌入式(适合读多写少的场景)
// users 集合示例文档
db.users.insertOne({
"_id": ObjectId(),
"username": "john_doe",
"email": "john@example.com",
"posts": [{
"_id": ObjectId(),
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": ISODate("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"],
"comments": [{
"user_id": ObjectId(),
"username": "alice",
"content": "写得很好!",
"created_at": ISODate("2024-01-16")
}]
}]
});
// 方案二:偏向引用式(适合写多读少的场景)
// users 集合
db.users.insertOne({
"_id": ObjectId(),
"username": "john_doe",
"email": "john@example.com"
});
// posts 集合
db.posts.insertOne({
"_id": ObjectId(),
"author_id": ObjectId(), // 引用 users._id
"author_name": "john_doe", // 扩展引用
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": ISODate("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"]
});
// comments 集合
db.comments.insertOne({
"_id": ObjectId(),
"post_id": ObjectId(), // 引用 posts._id
"user_id": ObjectId(), // 引用 users._id
"username": "alice", // 扩展引用
"content": "写得很好!",
"created_at": ISODate("2024-01-16")
});
// 2. 性能测试
// 方案一:嵌入式查询(一次查询获取所有信息)
db.users.find(
{ "posts.tags": "MongoDB" },
{ "posts.$": 1 }
).explain("executionStats");
// 方案二:引用式查询(需要聚合或多次查询)
db.posts.aggregate([
{ $match: { tags: "MongoDB" } },
{ $lookup: {
from: "comments",
localField: "_id",
foreignField: "post_id",
as: "comments"
}}
]).explain("executionStats");
// 3. Schema 验证规则
// 为 posts 集合创建验证规则
db.createCollection("posts", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["author_id", "author_name", "title", "content", "created_at", "tags"],
properties: {
author_id: {
bsonType: "objectId",
description: "作者ID必须是 ObjectId 类型且不能为空"
},
author_name: {
bsonType: "string",
description: "作者名称,必须是字符串且不能为空"
},
title: {
bsonType: "string",
minLength: 1,
maxLength: 100,
description: "标题长度必须在1-100字符之间"
},
content: {
bsonType: "string",
minLength: 1,
description: "内容不能为空"
},
created_at: {
bsonType: "date",
description: "创建时间,必须是日期类型"
},
tags: {
bsonType: "array",
minItems: 1,
uniqueItems: true,
items: {
bsonType: "string"
},
description: "标签必须是非空字符串数组,且不能重复"
}
}
}
},
validationLevel: "strict",
validationAction: "error"
});
// 4. 验证结果
// 测试有效文档(应该成功)
db.posts.insertOne({
author_id: ObjectId(),
author_name: "john_doe",
title: "测试文章",
content: "这是一篇测试文章",
created_at: new Date(),
tags: ["测试", "MongoDB"]
});
// 测试无效文档(应该失败)
db.posts.insertOne({
author_name: "john_doe", // 缺少 author_id
title: "测试文章",
content: "这是一篇测试文章",
created_at: new Date(),
tags: [] // 空标签数组,违反 minItems 规则
});
```

View File

@@ -0,0 +1,171 @@
# 环境搭建
本章将详细介绍如何在 Rocky Linux 操作系统上安装和配置 MongoDB以及如何管理 MongoDB 服务。正确的环境搭建是后续学习的基础。
---
## 安装部署
基于 Rocky Linux 9 系统上,推荐使用 `yum``dnf` 包管理器通过官方源进行安装,这样可以确保安装的是最新稳定版本,并且便于后续更新。
**步骤 1: 配置 MongoDB 的 YUM/DNF 仓库**
创建一个 `/etc/yum.repos.d/mongodb-org-7.0.repo` 文件,并添加以下内容:
```ini
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el9-7.0/
gpgcheck=0
enabled=1
```
**步骤 2: 安装 MongoDB**
执行以下命令安装 MongoDB 数据库及其相关工具:
```shell
# 清理缓存并安装
yum makecache
yum install -y mongodb-org
```
这个命令会安装以下几个包:
- `mongodb-org-server`: `mongod` 守护进程、配置文件和初始化脚本。
- `mongodb-org-mongos`: `mongos` 守护进程。
- `mongodb-mongosh`: MongoDB Shell (`mongosh`)。
- `mongodb-org-tools`: MongoDB 的命令行工具,如 `mongodump`, `mongorestore`, `mongoimport`, `mongoexport` 等。
---
## 相关配置
MongoDB 的主配置文件位于 `/etc/mongod.conf`,默认的配置文件采用 YAML 格式。以下是一些核心配置项的说明:
```yaml
# mongod.conf
# 存储设置
storage:
dbPath: /var/lib/mongo # 数据文件存放目录
journal:
enabled: true # 是否启用 journal 日志,建议始终开启
# 系统日志设置
systemLog:
destination: file # 日志输出到文件
logAppend: true # 日志以追加方式写入
path: /var/log/mongodb/mongod.log # 日志文件路径
# 网络设置
net:
port: 27017 # 监听端口
bindIp: 127.0.0.1 # 绑定的 IP 地址,默认为本地回环地址。如需远程访问,可设置为 0.0.0.0
# 进程管理
processManagement:
timeZoneInfo: /usr/share/zoneinfo # 时区信息
# 安全设置 (默认禁用)
#security:
# authorization: enabled # 启用访问控制
```
- **`storage.dbPath`**: MongoDB 数据文件的存储路径,需要确保 `mongod` 用户对此目录有读写权限。
- **`systemLog.path`**: 日志文件路径,用于记录数据库的操作和错误信息。
- **`net.bindIp`**: 这是非常重要的安全配置。默认 `127.0.0.1` 只允许本机连接。如果需要从其他服务器连接,应谨慎地将其设置为服务器的内网 IP 或 `0.0.0.0`(监听所有网络接口),并配合防火墙和安全组规则使用。
---
## 服务管理
在现代 Linux 系统中,我们使用 `systemd` 来管理 `mongod` 服务。
```shell
# 启动 MongoDB 服务
systemctl start mongod
# 查看服务状态
systemctl status mongod
# 停止 MongoDB 服务
systemctl stop mongod
# 重启 MongoDB 服务
systemctl restart mongod
# 启用开机自启动
systemctl enable mongod
# 禁用开机自启动
systemctl disable mongod
```
## 生产配置
这是一个更贴近生产环境的配置文件示例,增加了安全和性能相关的配置。
```yaml
# /etc/mongod.conf - Production Example
storage:
dbPath: /var/lib/mongo
journal:
enabled: true
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 1 # 根据服务器内存调整,建议为物理内存的 50% - 60%
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27017
bindIp: 127.0.0.1,192.168.1.100 # 绑定本地和内网 IP
maxIncomingConnections: 1000 # 最大连接数
processManagement:
fork: true # 后台运行
pidFilePath: /var/run/mongodb/mongod.pid
timeZoneInfo: /usr/share/zoneinfo
security:
authorization: enabled # 开启认证
replication:
replSetName: rs0 # 副本集名称
```
## 实践操作
### 需求描述
1. 使用 `root` 用户安装并正常运行
2. 更改数据目录 `dbPath``/data/mongodb`
3. 更改监听地址 `bindIp``0.0.0.0`
### 实践细节和结果验证
```shell
# 关闭防火墙和 SELINUX
# 安装 Mongodb 并正常启动
tee /etc/yum.repos.d/mongodb-org-7.0.repo << EOF
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el9-7.0/
gpgcheck=0
enabled=1
EOF
yum makecache
yum install -y mongodb-org
mkdir -p /data/mongodb
sed -i 's|dbPath: /var/lib/mongo|dbPath: /data/mongodb|' /etc/mongod.conf
sed -i 's|bindIp: 127.0.0.1|bindIp: 0.0.0.0|' /etc/mongod.conf
cp /lib/systemd/system/mongod.service /etc/systemd/system/mongod.service
sed -i 's/User=mongod/User=root/' /etc/systemd/system/mongod.service
sed -i 's/Group=mongod/Group=root/' /etc/systemd/system/mongod.service
systemctl start mongod && systemctl enable mongod
# 测试验证
mongod --version
```

View File

@@ -0,0 +1,267 @@
# 索引优化
索引是提升 MongoDB 查询性能的关键。本章节将深入探讨索引的类型、创建策略、如何分析查询性能以及索引的维护方法,帮助大家构建高性能的数据库应用。
---
## 索引基础
### 什么是索引?
索引是一种特殊的数据结构它以易于遍历的形式存储了集合中一小部分的数据。通过索引MongoDB 可以直接定位到符合查询条件的文档,而无需扫描整个集合,从而极大地提高了查询速度。
### 索引的类型
MongoDB 支持多种类型的索引,以适应不同的查询需求。
1. **单字段索引 (Single Field Index)**
- 最基础的索引类型,针对单个字段创建。
- 示例: `db.inventory.createIndex({ price: 1 })` (按价格升序)
2. **复合索引 (Compound Index)**
- 在多个字段上创建的索引。字段的顺序非常重要,决定了索引的查询效率。
- 示例: `db.inventory.createIndex({ price: 1, qty: -1 })` (先按价格升序,再按数量降序)
3. **多键索引 (Multikey Index)**
- 为数组字段创建的索引。MongoDB 会为数组中的每个元素创建一条索引项。
- 示例: `db.inventory.createIndex({ tags: 1 })` (为 `tags` 数组中的每个标签创建索引)
4. **文本索引 (Text Index)**
- 用于支持对字符串内容的文本搜索查询。
- 示例: `db.inventory.createIndex({ description: "text" })`
5. **地理空间索引 (Geospatial Index)**
- 用于高效地查询地理空间坐标数据。
- 类型: `2dsphere` (用于球面几何) 和 `2d` (用于平面几何)。
- 示例: `db.places.createIndex({ location: "2dsphere" })`
6. **哈希索引 (Hashed Index)**
- 计算字段值的哈希值并对哈希值建立索引,主要用于哈希分片。
- 示例: `db.inventory.createIndex({ status: "hashed" })`
---
## 索引策略与创建
### 创建索引
使用 `createIndex()` 方法在集合上创建索引。
```javascript
// 在 students 集合的 email 字段上创建一个唯一的升序索引
db.students.createIndex({ student_id: 1 }, { unique: true })
// 创建一个后台索引,避免阻塞数据库操作
db.students.createIndex({ name: 1 }, { background: true })
```
### 索引属性
- **`unique`**: 强制索引字段的值唯一,拒绝包含重复值的文档插入或更新。
- **`sparse`**: 稀疏索引,只为包含索引字段的文档创建索引项,节省空间。
- **`background`**: 在后台创建索引,允许在创建过程中进行读写操作(但会稍慢)。
- **`expireAfterSeconds`**: TTL (Time-To-Live) 索引,自动从集合中删除超过指定时间的文档。
### 复合索引的字段顺序
复合索引的字段顺序遵循 **ESR (Equality, Sort, Range)** 法则:
1. **等值查询 (Equality)** 字段应放在最前面。
2. **排序 (Sort)** 字段其次。
3. **范围查询 (Range)** 字段(如 `$gt`, `$lt`)应放在最后。
正确的字段顺序可以使一个索引服务于多种查询,提高索引的复用率。
**案例分析**:
假设我们有一个 `products` 集合,需要频繁执行一个查询:查找特定 `category` 的商品,按 `brand` 排序,并筛选出价格 `price` 大于某个值的商品。
**查询语句**:
```javascript
db.products.find(
{
category: "electronics", // 等值查询 (Equality)
price: { $gt: 500 } // 范围查询 (Range)
}
).sort({ brand: 1 }) // 排序 (Sort)
```
**根据 ESR 法则创建索引**:
遵循 ESR 法则,我们应该将等值查询字段 `category` 放在最前面,然后是排序字段 `brand`,最后是范围查询字段 `price`
**最佳索引**:
```javascript
db.products.createIndex({ category: 1, brand: 1, price: 1 })
```
**为什么这个顺序是最高效的?**
1. **等值查询优先 (`category`)**: MongoDB 可以立即通过索引定位到所有 `category``"electronics"` 的文档,快速缩小查询范围。
2. **排序字段次之 (`brand`)**: 在已筛选出的 `"electronics"` 索引条目中,数据已经按 `brand` 排好序。因此MongoDB 无需在内存中执行额外的排序操作 (`in-memory sort`),极大地提升了性能。
3. **范围查询最后 (`price`)**: 在已经按 `brand` 排序的索引条目上MongoDB 可以顺序扫描,高效地过滤出 `price` 大于 500 的条目。
如果索引顺序不当,例如 `{ price: 1, brand: 1, category: 1 }`MongoDB 将无法有效利用索引进行排序,可能导致性能下降。
---
## 查询性能分析
### `explain()` 方法
`explain()` 是分析查询性能的利器。它可以显示查询的执行计划,包括是否使用了索引、扫描了多少文档等信息。
```javascript
db.students.find({ age: { $gt: 21 } }).explain("executionStats")
```
### 分析 `explain()` 结果
关注 `executionStats` 中的关键指标:
- **`executionSuccess`**: 查询是否成功执行。
- **`nReturned`**: 查询返回的文档数量。
- **`totalKeysExamined`**: 扫描的索引键数量。
- **`totalDocsExamined`**: 扫描的文档数量。
- **`executionTimeMillis`**: 查询执行时间(毫秒)。
- **`winningPlan.stage`**: 查询计划的阶段。
- `COLLSCAN`: 全集合扫描,性能最低。
- `IXSCAN`: 索引扫描,性能较高。
- `FETCH`: 根据索引指针去获取文档。
**理想情况**: `totalKeysExamined``totalDocsExamined` 应该尽可能接近 `nReturned`
### 覆盖查询 (Covered Query)
当查询所需的所有字段都包含在索引中时MongoDB 可以直接从索引返回结果,而无需访问文档,这称为覆盖查询。覆盖查询性能极高。
**条件**:
1. 查询的所有字段都是索引的一部分。
2. 查询返回的所有字段都在同一个索引中。
3. 查询的字段中不包含 `_id` 字段,或者 `_id` 字段是索引的一部分(默认情况下 `_id` 会被返回,除非显式排除)。
**案例分析**:
假设我们有一个 `students` 集合,并且我们经常需要通过 `student_id` 查找学生的姓名 `name`
1. **创建复合索引**:
为了优化这个查询,我们可以在 `student_id``name` 字段上创建一个复合索引。
```javascript
db.students.createIndex({ student_id: 1, name: 1 })
```
2. **执行覆盖查询**:
现在,我们执行一个只查询 `student_id` 并只返回 `name` 字段的查询。我们使用投影 (projection) 来显式排除 `_id` 字段,并只包含 `name` 字段。
```javascript
db.students.find({ student_id: 'S1001' }, { name: 1, _id: 0 })
```
3. **性能验证**:
使用 `explain()` 方法来查看查询的执行计划。
```javascript
db.students.find({ student_id: 'S1001' }, { name: 1, _id: 0 }).explain('executionStats')
```
在 `executionStats` 的输出中,我们会发现:
- `totalDocsExamined` 的值为 `0`。这表明 MongoDB 没有扫描任何文档。
- `totalKeysExamined` 的值大于 `0`,说明扫描了索引。
- `winningPlan.stage` 会显示为 `IXSCAN`,并且没有 `FETCH` 阶段。
这个结果证明了该查询是一个覆盖查询。MongoDB 仅通过访问索引就获取了所有需要的数据,完全避免了读取文档的磁盘 I/O 操作,从而实现了极高的查询性能。
---
## 索引维护
### 查看索引
使用 `getIndexes()` 方法查看集合上的所有索引。
```javascript
db.students.getIndexes()
```
### 删除索引
使用 `dropIndex()` 方法删除指定的索引。
```javascript
// 按名称删除索引
db.students.dropIndex("email_1")
// 按键模式删除索引
db.students.dropIndex({ last_login: -1 })
```
### 索引大小和使用情况
使用 `$indexStats` 聚合阶段可以查看索引的大小和使用情况(自上次重启以来的操作次数)。
```javascript
db.students.aggregate([{ $indexStats: {} }])
```
---
## 实践操作
在本节中,我们将使用 `products_for_indexing` 集合进行实践,数据已在 `data.js` 中定义。
### 需求描述
假设我们有一个电商平台的 `products02` 集合,需要支持以下查询场景:
1. 频繁按商品类别 (`category`) 查找商品,并按价格 (`price`) 排序。
2. 需要快速获取特定类别和品牌的商品信息,且只关心品牌和价格。
### 实践细节和结果验证
```javascript
// 准备工作:确保 products_for_indexing 集合存在且包含数据
// (数据已在 data.js 中定义,请先加载)
// 1. 创建复合索引以优化排序查询
// 需求:按类别查找并按价格排序
db.products_for_indexing.createIndex({ category: 1, price: 1 });
// 2. 使用 explain() 分析查询性能
// -- 无索引查询 (模拟,假设索引未创建) --
// db.products_for_indexing.find({ category: 'Electronics' }).sort({ price: -1 }).explain('executionStats');
// 结果会显示 COLLSCAN (全表扫描),效率低
// -- 有索引查询 --
db.products_for_indexing.find({ category: 'Electronics' }).sort({ price: -1 }).explain('executionStats');
// **结果验证**:
// winningPlan.stage 应为 IXSCAN (索引扫描),表明使用了索引。
// totalDocsExamined 数量远小于集合总数,性能高。
// 3. 构造并验证覆盖查询
// 需求:只查询电子产品的品牌和价格
// -- 创建一个能覆盖查询的索引 --
db.products_for_indexing.createIndex({ category: 1, brand: 1, price: 1 });
// -- 执行覆盖查询 --
db.products_for_indexing.find(
{ category: 'Electronics', brand: 'Sony' },
{ brand: 1, price: 1, _id: 0 }
).explain('executionStats');
// **结果验证**:
// totalDocsExamined 应为 0表明没有读取文档。
// winningPlan.stage 为 IXSCAN且没有 FETCH 阶段,证明是高效的覆盖查询。
// 4. 索引维护
// -- 查看当前集合上的所有索引 --
db.products_for_indexing.getIndexes();
// -- 删除一个不再需要的索引 --
// 假设 { category: 1, price: 1 } 这个索引不再需要
db.products_for_indexing.dropIndex('category_1_price_1');
// -- 再次查看索引,确认已删除 --
db.products_for_indexing.getIndexes();
```

View File

@@ -0,0 +1,267 @@
# 聚合框架
聚合框架是 MongoDB 提供的一个强大的数据处理工具,它允许对集合中的数据进行一系列的转换和计算,最终得到聚合后的结果。本章节将深入探讨聚合管道、常用的聚合阶段以及如何利用聚合框架进行复杂的数据分析。
---
## 聚合管道Aggregation Pipeline
聚合操作的核心是聚合管道。管道由一个或多个**阶段 (Stage)** 组成,每个阶段都会对输入的文档流进行处理,并将结果传递给下一个阶段。
### 聚合管道的语法
聚合操作使用 `aggregate()` 方法,其参数是一个包含所有阶段的数组。
```javascript
db.collection.aggregate([
{ <stage1> },
{ <stage2> },
...
])
```
### 聚合管道的优势
- **功能强大**: 支持复杂的数据转换、分组、计算和重塑。
- **性能高效**: 许多操作在数据库服务端以原生代码执行,减少了数据在网络中的传输。
- **灵活性高**: 可以通过组合不同的阶段来满足各种复杂的数据处理需求。
---
## 常用聚合阶段
以下是一些最常用的聚合阶段,通过组合它们可以实现强大的数据处理能力。
### `$match`
- **功能**: 过滤文档,只将满足条件的文档传递给下一个阶段。类似于 `find()` 方法的查询条件。
- **建议**: 尽可能将 `$match` 放在管道的开头,以尽早减少需要处理的文档数量,提高效率。
- **示例**: 筛选出状态为 "A" 的订单。
```javascript
{ $match: { status: "A" } }
```
### `$project`
- **功能**: 重塑文档流。可以包含、排除、重命名字段,或者通过表达式计算新字段。
- **示例**: 只保留 `_id`、`name` 字段,并创建一个新的 `bonus` 字段。
```javascript
{ $project: { name: 1, bonus: { $multiply: ["$salary", 0.1] } } }
```
### `$group`
- **功能**: 按指定的 `_id` 表达式对文档进行分组,并对每个分组应用累加器表达式进行计算。
- **核心**: `_id` 字段定义了分组的键。
- **示例**: 按 `cust_id` 分组,并计算每个客户的订单总金额。
```javascript
{
$group: {
_id: "$cust_id",
totalAmount: { $sum: "$amount" }
}
}
```
### `$sort`
- **功能**: 对文档流进行排序,与 `find()` 中的 `sort()` 类似。
- **建议**: 如果需要排序,尽量在管道的早期阶段进行,特别是当排序字段有索引时。
- **示例**: 按 `totalAmount` 降序排序。
```javascript
{ $sort: { totalAmount: -1 } }
```
### `$limit` 和 `$skip`
- **功能**: 分别用于限制和跳过文档数量,实现分页。
- **示例**: 返回排序后的前 5 个结果。
```javascript
{ $limit: 5 }
```
### `$unwind`
- **功能**: 将文档中的数组字段拆分成多个文档,数组中的每个元素都会生成一个新的文档(与其他字段组合)。
- **示例**: 将 `tags` 数组拆分。
```javascript
// 输入: { _id: 1, item: "A", tags: ["x", "y"] }
{ $unwind: "$tags" }
// 输出:
// { _id: 1, item: "A", tags: "x" }
// { _id: 1, item: "A", tags: "y" }
```
### `$lookup`
- **功能**: 实现左外连接Left Outer Join将当前集合与另一个集合的文档进行关联。
- **示例**: 将 `orders` 集合与 `inventory` 集合关联起来。
```javascript
{
$lookup: {
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
```
---
## 聚合累加器表达式
累加器主要在 `$group` 阶段使用,用于对分组后的数据进行计算。
| 累加器 | 描述 |
| :--- | :--- |
| `$sum` | 计算总和 |
| `$avg` | 计算平均值 |
| `$min` | 获取最小值 |
| `$max` | 获取最大值 |
| `$first` | 获取每个分组的第一条文档的字段值 |
| `$last` | 获取每个分组的最后一条文档的字段值 |
| `$push` | 将字段值添加到一个数组中 |
| `$addToSet` | 将唯一的字段值添加到一个数组中 |
---
## 聚合管道优化
- **尽早过滤**: 将 `$match` 阶段放在管道的最前面。
- **尽早投影**: 使用 `$project` 移除不需要的字段,减少后续阶段的数据处理量。
- **利用索引**: 如果 `$match` 或 `$sort` 阶段的字段有索引MongoDB 可以利用它来优化性能。
- **避免在分片键上 `$unwind`**: 这可能会导致性能问题。
---
## 实践环节
### 需求描述
假设有一个 `sales` 集合,包含 `product`, `quantity`, `price`, `date` 字段。
1. **计算总销售额**: 计算每个产品的总销售额(`quantity * price`)。
2. **查找最畅销产品**: 按销售额降序排列,找出销售额最高的前 5 个产品。
3. **按月统计销售**: 按月份对所有销售数据进行分组,并计算每月的总销售额和平均订单金额。
4. **关联查询**: 假设还有一个 `products_for_aggregation` 集合(包含 `name`, `category`),使用 `$lookup` 将销售数据与产品类别关联起来,并按类别统计销售额。
5.
### 实践细节和结果验证
```javascript
// 准备工作:确保已在 mongo shell 中加载 data.js 文件
// 1. 计算每个产品的总销售额
db.sales.aggregate([
{
$group: {
_id: "$product",
totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
}
}
])
/*
预期结果:
[
{ _id: 'Mouse', totalRevenue: 125 },
{ _id: 'Keyboard', totalRevenue: 75 },
{ _id: 'Monitor', totalRevenue: 300 },
{ _id: 'Webcam', totalRevenue: 50 },
{ _id: 'Laptop', totalRevenue: 4700 }
]
*/
// 2. 查找最畅销的前 5 个产品(按销售额)
db.sales.aggregate([
{
$group: {
_id: "$product",
totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
}
},
{
$sort: { totalRevenue: -1 }
},
{
$limit: 5
}
])
/*
预期结果:
[
{ _id: 'Laptop', totalRevenue: 4700 },
{ _id: 'Monitor', totalRevenue: 300 },
{ _id: 'Mouse', totalRevenue: 125 },
{ _id: 'Keyboard', totalRevenue: 75 },
{ _id: 'Webcam', totalRevenue: 50 }
]
*/
// 3. 按月统计销售额
db.sales.aggregate([
{
$group: {
_id: { $month: "$date" }, // 按月份分组
totalMonthlyRevenue: { $sum: { $multiply: ["$quantity", "$price"] } },
averageOrderValue: { $avg: { $multiply: ["$quantity", "$price"] } }
}
},
{
$sort: { _id: 1 } // 按月份升序排序
}
])
/*
预期结果:
[
{ _id: 1, totalMonthlyRevenue: 1325, averageOrderValue: 441.666... },
{ _id: 2, totalMonthlyRevenue: 1675, averageOrderValue: 558.333... },
{ _id: 3, totalMonthlyRevenue: 2250, averageOrderValue: 1125 }
]
*/
// 4. 关联查询:按产品类别统计销售额
db.sales.aggregate([
// 阶段一: 计算每笔销售的销售额
{
$project: {
product: 1,
revenue: { $multiply: ["$quantity", "$price"] }
}
},
// 阶段二: 关联 products 集合获取类别信息
{
$lookup: {
from: "products_for_aggregation",
localField: "product",
foreignField: "name",
as: "productDetails"
}
},
// 阶段三: 展开 productDetails 数组
{
$unwind: "$productDetails"
},
// 阶段四: 按类别分组统计总销售额
{
$group: {
_id: "$productDetails.category",
totalCategoryRevenue: { $sum: "$revenue" }
}
}
])
/*
预期结果:
[
{ _id: 'Accessories', totalCategoryRevenue: 50 },
{ _id: 'Electronics', totalCategoryRevenue: 5200 }
]
*/
```

View File

@@ -0,0 +1,167 @@
# 查询进阶
本章节将带大家深入了解 MongoDB 强大的查询功能,包括使用查询操作符、投影、排序、分页以及正则表达式查询,可以更精确、更高效地从数据库中检索数据。
---
## 查询操作符
查询操作符是 MongoDB 查询语言的核心,它们可以帮助大家构建更复杂的查询条件。
### 比较操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$eq` | 等于 (Equal) | `db.inventory.find({ qty: { $eq: 20 } })` |
| `$ne` | 不等于 (Not Equal) | `db.inventory.find({ qty: { $ne: 20 } })` |
| `$gt` | 大于 (Greater Than) | `db.inventory.find({ qty: { $gt: 20 } })` |
| `$gte` | 大于等于 (Greater Than or Equal) | `db.inventory.find({ qty: { $gte: 20 } })` |
| `$lt` | 小于 (Less Than) | `db.inventory.find({ qty: { $lt: 20 } })` |
| `$lte` | 小于等于 (Less Than or Equal) | `db.inventory.find({ qty: { $lte: 20 } })` |
| `$in` | 在指定数组内 (In) | `db.inventory.find({ qty: { $in: [5, 15] } })` |
| `$nin` | 不在指定数组内 (Not In) | `db.inventory.find({ qty: { $nin: [5, 15] } })` |
### 逻辑操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$and` | 逻辑与,连接多个查询条件 | `db.inventory.find({ $and: [{ qty: { $lt: 20 } }, { price: { $gt: 10 } }] })` |
| `$or` | 逻辑或,满足任一查询条件 | `db.inventory.find({ $or: [{ qty: { $lt: 20 } }, { price: { $gt: 50 } }] })` |
| `$nor` | 逻辑非或,所有条件都不满足 | `db.inventory.find({ $nor: [{ price: 1.99 }, { sale: true }] })` |
| `$not` | 逻辑非,对指定条件取反 | `db.inventory.find({ price: { $not: { $gt: 1.99 } } })` |
### 元素操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$exists` | 判断字段是否存在 | `db.inventory.find({ qty: { $exists: true } })` |
| `$type` | 判断字段的数据类型 | `db.inventory.find({ qty: { $type: "number" } })` |
### 数组操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$all` | 匹配包含所有指定元素的数组 | `db.inventory.find({ tags: { $all: ["appliance", "school"] } })` |
| `$elemMatch` | 数组中至少有一个元素满足所有指定条件 | `db.inventory.find({ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } })` |
| `$size` | 匹配指定大小的数组 | `db.inventory.find({ tags: { $size: 3 } })` |
---
## 投影Projection
投影用于限制查询结果中返回的字段,可以减少网络传输的数据量,并保护敏感字段。
- **包含字段**: 在 `find()` 方法的第二个参数中,将需要返回的字段设置为 `1`
```javascript
// 只返回 name 和 price 字段_id 默认返回
db.products.find({}, { name: 1, price: 1 })
```
- **排除字段**: 将不需要返回的字段设置为 `0`。
```javascript
// 返回除 description 之外的所有字段
db.products.find({}, { description: 0 })
```
- **排除 `_id` 字段**:
```javascript
db.products.find({}, { name: 1, price: 1, _id: 0 })
```
---
## 排序Sorting
使用 `sort()` 方法对查询结果进行排序。
- **升序 (Ascending)**: 将字段值设置为 `1`。
- **降序 (Descending)**: 将字段值设置为 `-1`。
```javascript
// 按价格升序排序
db.products.find().sort({ price: 1 })
// 按库存降序、名称升序排序
db.products.find().sort({ stock: -1, name: 1 })
```
---
## 分页Pagination
通过组合使用 `limit()` 和 `skip()` 方法,可以实现对查询结果的分页。
- **`limit()`**: 限制返回的文档数量。
- **`skip()`**: 跳过指定数量的文档。
```javascript
// 获取第 2 页数据,每页 10 条 (跳过前 10 条,返回 10 条)
db.products.find().skip(10).limit(10)
```
**注意**: 对于大数据量的集合,使用 `skip()` 进行深度分页可能会有性能问题。在这种情况下,建议使用基于范围的查询(如使用 `_id` 或时间戳)。
---
## 正则表达式查询
MongoDB 支持使用 PCRE (Perl Compatible Regular Expression) 语法的正则表达式进行字符串匹配。
```javascript
// 查询 name 字段以 'A' 开头的文档 (不区分大小写)
db.products.find({ name: { $regex: '^A', $options: 'i' } })
// 查询 name 字段包含 'pro' 的文档
db.products.find({ name: /pro/ })
```
---
## 实践操作
假设有一个 `students` 集合,包含以下字段:`name`, `age`, `major`, `gpa`, `courses` (数组)。
### 需求描述
1. **查询练习**:
- 查询所有主修 'Computer Science' 且 GPA 大于 3.5 的学生。
- 查询年龄在 20 到 22 岁之间(含)的学生。
- 查询选修了 'Database Systems' 和 'Data Structures' 两门课的学生。
2. **投影练习**:
- 只返回学生的姓名和专业。
3. **排序和分页练习**:
- 按 GPA 降序显示前 10 名学生。
4. **正则表达式练习**:
- 查询所有姓 'Li' 的学生。
### 实践细节和结果验证
```javascript
// 1. 查询练习
// 查询所有主修 'Computer Science' 且 GPA 大于 3.5 的学生
db.students.find({ major: 'Computer Science', gpa: { $gt: 3.5 } });
// 查询年龄在 20 到 22 岁之间(含)的学生
db.students.find({ age: { $gte: 20, $lte: 22 } });
// 查询选修了 'Database Systems' 和 'Data Structures' 两门课的学生
db.students.find({ courses: { $all: ['Database Systems', 'Data Structures'] } });
// 2. 投影练习
// 只返回学生的姓名和专业
db.students.find({}, { name: 1, major: 1, _id: 0 });
// 3. 排序和分页练习
// 按 GPA 降序显示前 10 名学生
db.students.find().sort({ gpa: -1 }).limit(10);
// 4. 正则表达式练习
// 查询所有姓 'Li' 的学生
db.students.find({ name: /^Li/ });
```

View File

@@ -0,0 +1,145 @@
// This file contains sample data for all MongoDB practice sessions.
// Load this file into your mongo shell using: load('/path/to/this/file/data.js')
// --- Data for: MongoDB基础操作.md ---
db.inventory.drop();
db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A", tags: ["blank", "red"], price: 15, sale: false },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A", tags: ["school", "office"], price: 20, sale: true },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D", tags: ["office", "storage"], price: 10, sale: false },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D", tags: ["school", "organization"], price: 12, sale: true },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A", tags: ["appliance", "school", "storage"], price: 5, sale: false },
{ item: "mousepad", qty: 20, size: { h: 19, w: 22, uom: "cm" }, status: "P", price: 25, sale: true, results: [{ product: "xyz", score: 10 }, { product: "abc", score: 8 }] },
{ item: "keyboard", qty: 5, price: 55, sale: false, results: [{ product: "xyz", score: 7 }, { product: "abc", score: 6 }] },
{ item: "stapler", qty: 15, price: 1.99, sale: true, results: [{ product: "xyz", score: 9 }, { product: "abc", score: 4 }] }
]);
db.books.drop();
db.books.insertMany([
{
"title": "Dune",
"author": "Frank Herbert",
"published_year": 1965,
"genres": ["Science Fiction", "Fantasy"],
"stock": 8
},
{
"title": "Foundation",
"author": "Isaac Asimov",
"published_year": 1951,
"genres": ["Science Fiction"],
"stock": 15
},
{
"title": "1984",
"author": "George Orwell",
"published_year": 1949,
"genres": ["Science Fiction", "Dystopian"],
"stock": 5
},
{
"title": "Pride and Prejudice",
"author": "Jane Austen",
"published_year": 1813,
"genres": ["Romance", "Classic"],
"stock": 12
},
{
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"published_year": 1937,
"genres": ["Fantasy", "Adventure"],
"stock": 20
}
]);
// --- Data for: MongoDB进阶查询.md ---
db.students.drop();
db.students.insertMany([
{ student_id: 'S1001', name: 'Li Wei', age: 21, major: 'Computer Science', gpa: 3.8, courses: ['Database Systems', 'Data Structures', 'Operating Systems'] },
{ student_id: 'S1002', name: 'Zhang Min', age: 20, major: 'Computer Science', gpa: 3.6, courses: ['Algorithms', 'Computer Networks'] },
{ student_id: 'S1003', name: 'Wang Fang', age: 22, major: 'Mathematics', gpa: 3.9, courses: ['Calculus', 'Linear Algebra'] },
{ student_id: 'S1004', name: 'Li Juan', age: 20, major: 'Computer Science', gpa: 3.4, courses: ['Database Systems', 'Data Structures'] },
{ student_id: 'S1005', name: 'Chen Hao', age: 23, major: 'Physics', gpa: 3.2, courses: ['Mechanics', 'Electromagnetism'] },
{ student_id: 'S1006', name: 'Liu Yang', age: 21, major: 'Computer Science', gpa: 3.7, courses: ['Database Systems', 'Artificial Intelligence'] }
]);
// --- Data for: MongoDB索引优化.md ---
db.products_for_indexing.drop();
db.products_for_indexing.insertMany([
{ "category": "Electronics", "brand": "Sony", "price": 1200 },
{ "category": "Electronics", "brand": "Samsung", "price": 950 },
{ "category": "Electronics", "brand": "Apple", "price": 1500 },
{ "category": "Clothing", "brand": "Nike", "price": 150 },
{ "category": "Clothing", "brand": "Adidas", "price": 120 },
{ "category": "Books", "brand": "PublisherA", "price": 25 },
{ "category": "Books", "brand": "PublisherB", "price": 30 },
{ "category": "Electronics", "brand": "Sony", "price": 800 }
]);
// --- Data for: MongoDB聚合框架.md ---
db.sales.drop();
db.sales.insertMany([
{ "product": "Laptop", "quantity": 1, "price": 1200, "date": new Date("2023-01-15") },
{ "product": "Mouse", "quantity": 2, "price": 25, "date": new Date("2023-01-15") },
{ "product": "Keyboard", "quantity": 1, "price": 75, "date": new Date("2023-01-16") },
{ "product": "Laptop", "quantity": 1, "price": 1300, "date": new Date("2023-02-10") },
{ "product": "Monitor", "quantity": 1, "price": 300, "date": new Date("2023-02-12") },
{ "product": "Mouse", "quantity": 3, "price": 25, "date": new Date("2023-02-20") },
{ "product": "Laptop", "quantity": 2, "price": 1100, "date": new Date("2023-03-05") },
{ "product": "Webcam", "quantity": 1, "price": 50, "date": new Date("2023-03-07") }
]);
db.products_for_aggregation.drop();
db.products_for_aggregation.insertMany([
{ "name": "Laptop", "category": "Electronics" },
{ "name": "Mouse", "category": "Electronics" },
{ "name": "Keyboard", "category": "Electronics" },
{ "name": "Monitor", "category": "Electronics" },
{ "name": "Webcam", "category": "Accessories" },
{ "name": "T-Shirt", "category": "Apparel" }
]);
// --- Data for: MongoDB数据建模.md ---
// Note: The following commands use shell-specific functions like ObjectId() and ISODate().
// They are intended to be run directly in the mongo shell.
db.users.drop();
db.posts.drop();
db.comments.drop();
const userJohnId = new ObjectId();
db.users.insertOne({
"_id": userJohnId,
"username": "john_doe",
"email": "john@example.com"
});
const postMongoId = new ObjectId();
db.posts.insertOne({
"_id": postMongoId,
"author_id": userJohnId,
"author_name": "john_doe",
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": new Date("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"]
});
const userAliceId = new ObjectId();
db.users.insertOne({
"_id": userAliceId,
"username": "alice",
"email": "alice@example.com"
});
db.comments.insertOne({
"_id": new ObjectId(),
"post_id": postMongoId,
"user_id": userAliceId,
"username": "alice",
"content": "写得很好!",
"created_at": new Date("2024-01-16")
});
print("All sample data have been loaded.");

View File

@@ -0,0 +1,65 @@
# MongoDB 课程文档规范
本文档旨在定义 MongoDB 课程相关文件的用途、格式风格和编写原则,以确保课程内容的一致性、专业性和易读性。
---
## 1. 文件定义
### 1.1. 课程大纲 (`mongodb_tpl.md`)
- **用途**:作为 MongoDB 课程的整体教学计划和结构指南。它定义了课程的章节、知识点、学时分配和实践环节。
- **目标读者**:课程开发者、上课教师。
- **核心要求**:结构清晰,逻辑性强,全面覆盖 MongoDB 的核心知识体系。
### 1.2. 课程内容 (`n_MongoDB.md`)
- **用途**:根据课程大纲,详细阐述每个知识点的具体内容,包括理论讲解、代码示例、实践操作和练习题。
- **目标读者**:学生。
- **核心要求**:内容详实,通俗易懂,理论与实践相结合。
---
## 2. 编写原则
### 2.1. 内容组织:从浅入深
课程内容应遵循认知规律,从基础概念开始,逐步深入到高级主题和实战应用。
- **基础先行**:首先介绍 NoSQL 和 MongoDB 的基本概念、架构和安装部署。
- **核心操作**:然后讲解 CRUD 操作、查询和索引等核心技能。
- **高级进阶**:接着深入数据建模、聚合框架、副本集和分片等高级主题。
- **实战应用**:最后通过项目实战,巩固所学知识,培养解决实际问题的能力。
### 2.2. 语言风格:通俗易懂
- **简化复杂概念**:使用简单的语言和比喻来解释复杂的技术概念。
- **图文并茂**:适当使用图表、流程图和示意图,帮助理解抽象的知识。
- **代码注释**:所有代码示例都应有清晰的注释,解释代码的功能和逻辑。
### 2.3. 理论与实践融合
- **理论指导实践**:每个理论知识点都应配有相应的实践案例或代码示例。
- **实践巩固理论**:通过实验、练习和项目,加深对理论知识的理解和应用。
- **场景驱动**:结合真实的应用场景(如电商、社交、物联网等)进行讲解,激发学习兴趣。
### 2.4. 格式风格:统一规范
- **Markdown 语法**:统一使用 Markdown 进行文档编写。
- **标题层级**:遵循清晰的标题层级结构(`#``##``###`),与课程大纲保持一致
- **代码块**:使用代码块来格式化代码示例,并注明语言类型。(bash/shell 统一用 shell)
- **术语规范**:专业术语首次出现时应予以解释,并保持中英文术语的统一性。
- **实践操作**:每一章内容最后有一个实践环节,标题为 **实践操作**,有需求描述、实践细节和结果验证。并将实践细节和结果验证放到同一个代码块中。
- **其他规范**
- 从 MongoDB 基础概念开始作为 `#` 标题,其他平级标题也用 `#` 格式
- 不需要课程概述
---
## 3. 协作流程
1. **大纲评审**:首先共同评审和确定 `mongodb_tpl.md` 中的课程大纲。
2. **内容开发**:根据大纲,分工协作编写 `n_MongoDB.md` 的具体内容。
3. **内容评审**定期进行内容评审Peer Review确保内容质量和风格统一。
4. **持续迭代**:根据教学反馈和技术发展,持续更新和完善课程内容。

View File

@@ -0,0 +1,373 @@
# 基础概念
## NoSQL 数据库概述
- 关系型数据库 vs NoSQL 数据库
- NoSQL 数据库分类(文档型、键值型、列族型、图形型)
- MongoDB 在 NoSQL 生态中的定位
## 核心概念
- 文档Document与 BSON 格式
- 集合Collection概念
- 数据库Database结构
- MongoDB 与关系型数据库术语对比
## 架构原理
- MongoDB 存储引擎WiredTiger
- 内存映射文件系统
- 索引机制
- 查询优化器
## 文档学习
---
# 环境搭建
## 安装部署
- Linux 环境安装RockyLinux9
## 相关配置
- 配置文件详解
- 数据目录设置
- 日志配置
- 网络绑定设置
## 服务管理
- systemctl 服务管理
- 开机自启动配置
- 服务状态监控
## 实践环节
- 生产环境配置文件模板
---
# 基础操作
## MongoDB Shell 使用
- mongo shell 连接
- 基本命令操作
- 脚本执行
## 数据库操作
- 创建和删除数据库
- 数据库列表查看
- 数据库切换
## 集合操作
- 创建和删除集合
- 集合属性设置
- 集合统计信息
## 文档 CRUD 操作
- 插入文档insert、insertOne、insertMany
- 查询文档find、findOne
- 更新文档update、updateOne、updateMany
- 删除文档remove、deleteOne、deleteMany
## 实践环节
- 基础 CRUD 操作练习
- 批量数据导入导出
- 数据迁移实验
---
# 进阶查询
## 查询操作符
- 比较操作符($eq、$ne、$gt、$gte、$lt、$lte
- 逻辑操作符($and、$or、$not、$nor
- 元素操作符($exists、$type
- 数组操作符($in、$nin、$all、$size
## 查询修饰符
- 排序sort
- 限制limit
- 跳过skip
- 投影projection
## 正则表达式查询
- 正则表达式语法
- 模糊查询实现
- 性能考虑
## 聚合框架基础
- 聚合管道概念
- 常用聚合操作符
- 分组和统计
## 实践环节
- 复杂查询练习
- 聚合查询实例
- 查询性能测试
---
# 索引优化
## 索引基础
- 索引原理和作用
- B-Tree 索引结构
- 索引的优缺点
## 索引类型
- 单字段索引
- 复合索引
- 多键索引
- 文本索引
- 地理空间索引
- 哈希索引
## 索引管理
- 创建索引createIndex
- 删除索引dropIndex
- 查看索引getIndexes
- 重建索引reIndex
## 查询性能优化
- 执行计划分析explain
- 索引选择策略
- 查询优化技巧
## 实践环节
- 索引创建和管理
- 性能测试对比
- 查询优化案例
---
# 数据建模
## 文档设计原则
- 嵌入式文档 vs 引用
- 数据冗余策略
- 原子性考虑
## Schema 设计模式
- 一对一关系建模
- 一对多关系建模
- 多对多关系建模
- 树形结构建模
## 数据验证
- Schema 验证规则
- 数据类型约束
- 自定义验证器
## 实践环节
- 电商系统数据建模
- 社交网络数据建模
- 内容管理系统建模
---
# 聚合框架
## 聚合管道详解
- $match 阶段
- $group 阶段
- $sort 阶段
- $project 阶段
- $limit 和 $skip 阶段
## 高级聚合操作
- $lookup关联查询
- $unwind数组展开
- $facet多维聚合
- $bucket分桶聚合
## 聚合表达式
- 算术表达式
- 字符串表达式
- 日期表达式
- 条件表达式
## MapReduce
- MapReduce 概念
- 编写 Map 和 Reduce 函数
- 性能对比
## 实践环节
- 数据分析报表
- 实时统计系统
- 复杂业务逻辑实现
---
# 副本集Replica Set
## 副本集概念
- 高可用性需求
- 副本集架构
- 主从复制原理
## 副本集架构
- Primary 节点
- Secondary 节点
- Arbiter 节点
- 选举机制
## 副本集部署
- 单机多实例部署
- 多机部署
- 配置文件设置
- 初始化副本集
## 副本集管理
- 添加和删除节点
- 优先级设置
- 故障转移测试
- 数据同步监控
## 实践环节
- 副本集搭建
- 故障模拟和恢复
- 性能测试
---
# 分片Sharding
## 分片概念
- 水平扩展需求
- 分片架构组件
- 分片键选择
## 分片架构
- Config Server
- Shard Server
- mongos 路由
- 数据分布策略
## 分片部署
- 分片集群搭建
- 配置服务器部署
- 路由服务器配置
- 分片初始化
## 分片管理
- 分片键管理
- 数据均衡
- 分片添加和删除
- 性能监控
## 实践环节
- 分片集群搭建
- 数据分片测试
- 扩容演练
---
# 安全管理
## 认证机制
- 用户认证
- 角色权限管理
- LDAP 集成
## 授权控制
- 基于角色的访问控制RBAC
- 数据库级权限
- 集合级权限
## 审计日志
- 审计功能配置
- 日志分析
- 合规性要求
## 实践环节
- 安全配置实施
- 权限测试
- 安全审计
---
# 备份与恢复
## 备份策略
- 全量备份
- 增量备份
- 实时备份
## 备份工具
- mongodump/mongorestore
- 文件系统快照
- 第三方备份工具
## 恢复策略
- 完全恢复
- 时间点恢复
- 选择性恢复
## 备份自动化
- 备份脚本编写
- 定时任务配置
- 备份验证
## 实践环节
- 备份恢复演练
- 自动化脚本开发
- 灾难恢复测试
---
# 性能监控与调优
## 性能监控
- mongostat 工具
- mongotop 工具
- 第三方监控工具
## 性能指标
- 连接数监控
- 操作延迟
- 内存使用
- 磁盘 I/O
## 性能调优
- 查询优化
- 索引优化
- 硬件优化
- 配置优化
## 故障诊断
- 慢查询分析
- 锁竞争分析
- 内存泄漏排查
## 实践环节
- 性能基准测试
- 瓶颈分析
- 调优实施
---