MongoDB 快速实战与基本原理
MongoDB 是目前最流行的 NoSQL 数据库之一,其文档型数据模型和灵活的 Schema 设计使其在现代应用开发中占据重要地位。本文将从核心概念、基本操作、性能优化和高可用架构四个维度,全面介绍 MongoDB 的实战要点与底层原理。
为什么要用 MongoDB
MongoDB 相比传统关系型数据库,具有以下核心优势:
Schema-less(无模式)特性 允许文档拥有不同的字段结构,非常适合需求频繁变化的业务场景。例如,用户信息可能包含:
json
{
"username": "user1",
"age": 28
}
而另一条可以完全不同:
json
{
"username": "user2",
"age": 25,
"bio": "热爱技术",
"tags": ["Java", "Go", "MongoDB"]
}
这种"读时模式"的特性,将数据结构的灵活性完全交给应用层控制。
水平扩展能力 是 MongoDB 的另一大优势。通过分片机制,可以将数据分散到多台机器上,轻松应对数据量增长带来的挑战。
核心概念对照
下表对比了关系型数据库与 MongoDB 的核心概念:
| SQL 概念 | MongoDB 概念 | 说明 |
|---|---|---|
| Database | Database | 数据库 |
| Table | Collection | 表 → 集合 |
| Row | Document | 行 → 文档 |
| Column | Field | 列 → 字段 |
| Index | Index | 索引 |
| Primary Key | _id (自动生成) | 主键 |
数据模型设计示例:
在"用户-订单"场景中,可以采用嵌套文档设计:
json
{
"_id": ObjectId("..."),
"username": "张三",
"orders": [
{
"orderId": "ORD20240101",
"amount": 299,
"items": ["MongoDB实战", "Redis设计与实现"]
},
{
"orderId": "ORD20240102",
"amount": 159,
"items": ["架构整洁之道"]
}
]
}
嵌套文档设计适用于订单数量较少、且经常需要与用户信息一起查询的场景。对于数据量较大的场景,建议单独建立订单集合,通过引用关联。
基本操作实战
数据库和集合管理
javascript
// 查看所有数据库
show dbs
// 切换/创建数据库
use myapp
// 查看当前数据库的集合
show collections
// 显式创建集合
db.createCollection("users")
CRUD 操作
插入操作:
javascript
// 单条插入
db.users.insertOne({
username: "张三",
email: "zhangsan@example.com",
tags: ["MongoDB", "Node.js"],
createdAt: new Date()
})
// 批量插入
db.users.insertMany([
{ username: "王五", email: "wangwu@example.com" },
{ username: "李四", email: "lisi@example.com" }
])
查询操作:
javascript
// 查询所有文档
db.users.find()
// 条件查询
db.users.find({ username: "张三" })
// 嵌套字段查询
db.users.find({ "orders.amount": { $gt: 200 } })
// 指定返回字段
db.users.find({ username: "张三" }, { username: 1, email: 1 })
// 分页查询
db.users.find().skip(10).limit(5)
更新操作:
javascript
// 单条更新
db.users.updateOne(
{ username: "张三" },
{ $set: { email: "newemail@example.com" } }
)
// 批量更新
db.users.updateMany(
{ tags: "MongoDB" },
{ $set: { isActive: true } }
)
// 数组操作
db.users.updateOne(
{ username: "张三" },
{ $push: { tags: "Python" } }
)
删除操作:
javascript
// 删除单条
db.users.deleteOne({ username: "王五" })
// 批量删除
db.users.deleteMany({ isActive: false })
常用查询操作符
比较操作符:
javascript
{ age: { $eq: 25 } } // 等于
{ age: { $ne: 25 } } // 不等于
{ age: { $gt: 18 } } // 大于
{ age: { $gte: 18 } } // 大于等于
{ age: { $lt: 30 } } // 小于
{ age: { $in: [20, 25, 30] } } // 在数组中
逻辑操作符:
javascript
{ $and: [{ age: { $gt: 18 } }, { isActive: true }] }
{ $or: [{ username: "王五" }, { email: /@example.com$/ }] }
{ $not: { isDeleted: true } }
数组操作符:
javascript
{ tags: "MongoDB" } // 数组包含
{ tags: { $all: ["MongoDB", "Node.js"] } } // 包含所有元素
{ tags: { $size: 3 } } // 数组长度为3
正则表达式:
javascript
{ username: { $regex: "^张" } }
{ email: { $options: "i", $regex: "example" } } // i表示不区分大小写
索引:性能优化的关键
索引是 MongoDB 性能优化的核心手段。合理的索引设计可以显著提升查询效率。
索引类型
javascript
// 单字段索引
db.users.createIndex({ email: 1 })
// 复合索引(字段顺序影响查询效率)
db.users.createIndex({ username: 1, createdAt: -1 })
// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// TTL 索引(自动过期删除,适用于会话、日志等场景)
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 * 24 * 7 } // 7天后自动删除
)
// 多键索引(针对数组字段)
db.users.createIndex({ tags: 1 })
复合索引设计原则
复合索引的字段顺序至关重要,遵循以下原则:
- 等值查询字段放在前面 :在查询
{ type: "vip", age: { $gt: 18 } }中,type 是等值查询,应放在前面 - 范围查询字段放在后面:age 的范围查询应放在后面
正确顺序:
javascript
db.users.createIndex({ type: 1, age: 1 }) // 适用于 { type: "vip" } 和 { type: "vip", age: { $gt: 18 } }
错误顺序:
javascript
db.users.createIndex({ age: 1, type: 1 }) // 不适用于单独的 type 查询
查询计划分析
javascript
// 查看查询执行计划
db.users.find({ username: "张三" }).explain("executionStats")
// 分析关键指标
// - executionTimeMillis: 执行时间
// - totalDocsExamined: 检查的文档数
// - indexName: 使用的索引
聚合管道:数据处理的瑞士军刀
聚合管道是 MongoDB 进行复杂数据处理的核心功能,通过多个阶段的组合实现灵活的数据转换。
基础语法
javascript
db.users.aggregate([
{ $match: { isActive: true } }, // 过滤阶段
{ $group: { // 分组阶段
_id: "$tags",
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } }, // 排序阶段
{ $limit: 10 } // 限制阶段
])
实战案例
案例一:统计每个标签下的用户数量
javascript
db.users.aggregate([
{ $unwind: "$tags" }, // 展开数组
{
$group: {
_id: "$tags",
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } },
{ $limit: 10 }
])
案例二:按月统计订单销售额
javascript
db.orders.aggregate([
{
$match: {
status: "completed",
createdAt: {
$gte: new Date("2024-01-01"),
$lt: new Date("2025-01-01")
}
}
},
{
$group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" }
},
totalAmount: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgAmount: { $avg: "$amount" }
}
},
{
$project: {
_id: 0,
period: { $concat: ["$_id.year", "-", { $toString: "$_id.month" }] },
totalAmount: 1,
orderCount: 1,
avgAmount: { $round: ["$avgAmount", 2] }
}
},
{ $sort: { period: 1 } }
])
聚合优化策略
- $match 前置:将过滤条件尽量放在管道前部,减少后续阶段处理的数据量
- $limit 前置:在确定只需要部分结果时,提前 limit
- $project 精简:只输出需要的字段,减少内存占用
高可用架构:副本集
副本集是 MongoDB 实现高可用的基础架构,提供数据冗余和故障自动切换能力。
架构组成
| 节点类型 | 功能 | 数据存储 |
|---|---|---|
| Primary | 处理所有写操作 | 是 |
| Secondary | 从主节点复制数据 | 是 |
| Arbiter | 仅参与投票 | 否 |
故障切换机制
当 Primary 节点宕机时,Secondary 节点会自动发起选举,选举出新的 Primary。整个过程对应用透明,通常在秒级完成恢复。
读写分离配置
javascript
// 默认配置:所有读写操作指向 Primary
// 设置优先从 Secondary 读取(适用于读多写少场景)
db.getMongo().setReadPreference('secondaryPreferred')
// 只从 Secondary 读取
db.getMongo().setReadPreference('secondary')
// 最近优先(适用于多机房部署)
db.getMongo().setReadPreference('nearest')
水平扩展:分片集群
当单机存储容量和处理能力无法满足需求时,分片提供了水平扩展能力。
分片原理
MongoDB 将数据划分为多个 chunk,每个 chunk 存储在不同的分片上。mongos 路由服务负责将查询请求路由到正确的分片。
分片键选择原则
- 高基数:取值范围要大,能分散到足够多的分片
- 数据分布均匀:避免写入集中在某个分片造成热点
- 查询本地化:常用查询条件包含分片键,减少广播查询
推荐方案:
javascript
// 用户表:使用用户ID
sh.shardCollection("myapp.users", { userId: "hashed" })
// 日志表:使用时间戳
sh.shardCollection("myapp.logs", { timestamp: 1 })
// 订单表:复合分片键
sh.shardCollection("myapp.orders", { userId: 1, orderId: 1 })
分片注意事项
- 不建议使用自增 ID 作为分片键,会导致新数据集中写入最后一个分片
- 使用
hashed分片键可以获得更好的写入分布 - 定期监控
Chunk分裂和迁移情况
实战最佳实践
文档设计规范
- 控制嵌套层级:建议嵌套层级不超过两层
- 文档大小限制:单个文档建议不超过 16MB
- 数组字段控制:避免使用无限制增长的数组
索引设计规范
- 数量控制:每个集合的索引数量建议不超过 10 个
- 定期分析 :使用
db.currentOp()分析慢查询 - 索引维护:定期检查并删除不再使用的索引
性能监控指标
javascript
// 查看当前操作
db.currentOp()
// 查看集合统计信息
db.users.stats()
// 查看数据库状态
db.stats()
// 查看慢查询阈值
db.getProfilingLevel()
db.getProfilingStatus()
备份策略
- mongodump/mongorestore:适合日常备份和恢复
- 副本集 Oplog:实现增量备份
- 文件系统快照:适合大规模数据备份
总结
MongoDB 以其灵活的文档模型、强大的查询能力和完善的高可用机制,成为现代应用开发的重要选择。掌握核心概念、熟练运用 CRUD 操作、合理设计索引、以及理解聚合管道的使用方法,是熟练运用 MongoDB 的关键。在生产环境中,还需要关注副本集和分片的合理配置,确保系统的高可用和可扩展性。