目录
一、概念
MongoDB是文档数据库,基本存储单元是 文档(Document),以BSON格式(一种类json的二进制形式)存储,文档可以包含键值对、嵌套文档、数组等复杂结构,字段可以动态扩展,需要在代码层管理数据约束,适合存储非结构化数据或半结构化数据(如日志、IoT 传感器数据、实时分析)。
文档结构示例:
{
"_id": ObjectId("507f191e810c19729de860ea"), // MongoDB 自动生成的唯一主键
"name": "Alice Smith",
"age": 30,
"email": "alice@example.com",
"address": {
"street": "123 Main St",
"city": "Anytown",
"zip": "12345"
},
"hobbies": ["reading", "hiking", "photography"],
"created_at": ISODate("2023-10-27T10:00:00Z")
}
设计目标:是解决关系型数据库在处理海量数据、高并发、灵活数据结构时遇到的扩展性、性能瓶颈和模式僵化问题。
MongoDB与关系型数据库RDBMS对比:
文档 (Document): 数据存储和操作的基本单元,类似于 JSON 对象,结构是键值对。
集合 (Collection):一组相关文档的容器,同一个集合(Collection)中的文档不要求具有相同的结构(模式自由/动态模式)。字段可以动态添加、修改或删除。
数据库 (Database):包含多个集合的物理容器,用于逻辑上隔离不同的应用或数据集,每个数据库有自己的用户、权限、集合和索引。
MongoDB中Document中可以出现的数据类型:
二、架构
2.1 逻辑结构
MongoDB与MySQL架构相差不多,底层都使⽤了可插拔的存储引擎以满用户的不同需要。最新版本的 MongoDB 中使用了 WiredTiger 作为默认的存储引擎,WiredTiger 提供了不同粒度的并发控制和压缩机制,能够为不同种类的应用提供了最好的性能和存储率。
在存储引擎上层的就是 MongoDB 的数据模型和查询语言了,由于 MongoDB 对数据的存储与 RDBMS 有较大的差异,所以它创建了一套不同的数据模型和查询语言。
2.2 数据模型
MongoDB的数据模型有两种
1> 内嵌:把相关联的数据保存在同一个文档结构中
适用场景:一对一或一对少关系,高频查询的子数据。
示例:用户档案中直接嵌套地址信息
{
"user_id": 1001,
"profile": {
"birthdate": "1990-05-15",
"education": "硕士"
}
}
优点:单次查询获取全部数据,读写高效。
缺点:文档大小可能膨胀(上限 16MB)。
2> 引用:通过存储数据引用信息来实现两个不同文档之间的关联
适用场景:一对多或多对多关系,子数据独立更新频繁。
示例:用户与订单通过
_id
关联
// users 集合
{
"_id": ObjectId("662a1b87c1b6e32a50f1a9e2"),
"name": "张三",
"email": "zhangsan@example.com"
}
// orders 集合
{
"_id": ObjectId("55f14312c7447c3da7051b27"),
"user_id": ObjectId("662a1b87c1b6e32a50f1a9e2"), // 引用用户ID
"items": ["手机", "耳机"],
"total": 5999
}
// 关联查询:获取所有订单及其对应的用户信息
db.orders.aggregate([
{
$lookup: {
from: "users", // 关联的集合名
localField: "user_id", // 当前集合的关联字段
foreignField: "_id", // 目标集合的关联字段
as: "user_info" // 输出字段名(数组)
}
}
])
// 输出结果
{
"_id": ObjectId("55f14312c7447c3da7051b27"),
"user_id": ObjectId("662a1b87c1b6e32a50f1a9e2"),
"items": ["手机", "耳机"],
"total": 5999,
"user_info": [ // 注意:结果总是数组
{
"_id": ObjectId("662a1b87c1b6e32a50f1a9e2"),
"name": "张三",
"email": "zhangsan@example.com"
}
]
}
优点:数据冗余少,易于维护一致性。
缺点:需多次查询(
$lookup
聚合操作可缓解)。
2.3 存储引擎:WiredTiger
存储引擎负责管理如何在磁盘上存储数据、处理读写操作、实现事务、管理内存缓存等。
MongoDB支持的存储引擎有MMAPv1,WiredTiger和InMemory。InMemory存储引擎用于将数据只存储在内存中,只将少量的元数据(meta-data)和诊断日志(Diagnostic)存储到硬盘文件中,由于不需要Disk的IO操作,就能获取所需的数据,InMemory存储引擎大幅度降低了数据查询的延迟(Latency)。从mongodb3.2开始默认的存储引擎是WiredTiger,3.2版本之前的默认存储引擎是MMAPv1,mongodb4.x版本不再支持MMAPv1存储引擎。
核心原理:
文档级并发控制: 这是 WiredTiger 高性能的关键。它允许对集合中的不同文档进行并发读写操作(写操作在文档级别加锁),极大地提高了多核CPU环境下的吞吐量。相比之前的 MMAPv1 引擎(集合级锁),这是质的飞跃。
写操作流程
数据写入 Journal 预写日志(防崩溃)
更新内存中的 Cache
后台线程异步刷盘:
每 60 秒生成 Checkpoint
压缩后写入数据文件
写优化与持久性:
Write Ahead Logging (WAL): 所有写操作首先被顺序、快速地写入一个持久化的 Journal 文件(并默认写入Cache)。这确保了即使发生崩溃,也能根据 Journal 恢复未刷盘的数据,保证操作的持久性(Durability)。
内存管理: 使用缓存(Cache) 来存储频繁访问的数据和索引(通过 LRU 算法管理)。写操作先在内存的 Cache 中进行修改,然后异步写入 Journal 和最终刷入数据文件。读操作优先从 Cache 读取。
Checkpoints: WiredTiger 定期(默认 60 秒或 Journal 达到 2GB)将内存中修改过的数据(脏页)以一致的状态快照写入数据文件。Checkpoint 是数据的持久化点,缩短了崩溃恢复时需要从 Journal 重放的操作量。
压缩: WiredTiger 支持对数据(Snappy 或 Zlib)和索引(Prefix)进行压缩,显著节省磁盘空间并减少 I/O。
B-Tree 索引存储: WiredTiger 使用 B-Tree 数据结构存储索引(这是 MongoDB 索引的主要实现方式)。
WiredTiger 关键配置 (
mongod.conf
)
storage:
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 10 # 内存缓存大小 (建议为物理内存50%)
journalCompressor: snappy # 日志压缩算法
collectionConfig:
blockCompressor: zstd # 数据压缩算法 (zstd平衡性能/压缩比)
indexConfig:
prefixCompression: true # 索引前缀压缩
三、事务
支持范围: MongoDB 4.0+ 支持多文档事务(ACID),适用于副本集;MongoDB 4.2+ 支持分布式事务(跨分片事务)。
实现原理:
使用快照隔离 (Snapshot Isolation) 级别。事务看到的是事务开始时数据库的一致性快照。
在 WiredTiger 存储引擎中,事务通过 MVCC (多版本并发控制) 实现。写操作在事务提交前对其他事务不可见。
涉及跨分片事务时,由协调者(通常是发起事务的 Mongos 或 Mongod)使用 Two-Phase Commit (2PC) 协议协调所有参与分片。
注意事项: 虽然提供了 ACID 保证,但分布式事务比单文档操作开销大得多,应谨慎使用,避免长时间运行的事务。单文档操作天生具有原子性。
举例:
const session = db.getMongo().startSession(); // 1. 创建会话
session.startTransaction({ // 2. 启动事务
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
try {
const coll = session.getDatabase("test").users;
coll.updateOne({ name: "Alice" }, { $inc: { balance: -100 } }); // 3. 操作1
coll.updateOne({ name: "Bob" }, { $inc: { balance: 100 } }); // 4. 操作2
session.commitTransaction(); // 5. 提交事务
} catch (error) {
session.abortTransaction(); // 6. 出错回滚
}