04 特色及优势
| 维度 | 能力 |
|---|---|
| 数据模型 | 多形性(同集合不同字段)、动态性(在线改模式)、JSONSchema 治理 |
| 开发体验 | 单存储区读写、反范式 + 无关联优化、API 自然 |
| 高可用 | Replica Set(2~50 节点,建议奇数)、自恢复、多中心容灾、滚动维护 |
| 横向扩展 | 无缝扩容、应用透明、多种数据分布策略,可达 TB-PB |
06 基本操作
环境与样本数据:
tar -xvf dump.tar.gz
mongorestore --uri="mongodb://root:root@10.130.0.12/?&authMechanism=SCRAM-SHA-1"
CRUD
// 插入
db.fruit.insertOne({ name: "apple" })
db.fruit.insertMany([{ name: "apple" }, { name: "pear" }, { name: "orange" }])
// 查询
db.customers.find({ username: "fmiller", name: "Elizabeth Ray" })
db.customers.find({ username: /^f/ })
db.customers.find({ $or: [{ username: /^f/ }, { name: /^E/ }] })
// 投影(projection)
db.customers.find({ username: /^f/ }, { name: 0, email: 0 }) // 排除
db.customers.find({ username: /^f/ }, { _id: 1, name: 1 }) // 仅返回
// 删除
db.customers.remove({ username: "abrown" })
// 更新
db.customers.updateOne({ username: "fmiller" }, { $set: { from: "China" } })
db.customers.updateMany({ username: "fmiller" }, { $set: { from: "China" } })
// 表 / 库管理
db.fruit.drop()
show collections
db.dropDatabase()
show dbs
SQL ↔ Mongo 操作符对照
| SQL | Mongo |
|---|---|
a <> 1 | {a: {$ne: 1}} |
a > 1 / >= | {a: {$gt: 1}} / {$gte: 1} |
a < 1 / <= | {a: {$lt: 1}} / {$lte: 1} |
a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |
a IS NULL | {a: {$exists: false}} |
a IN (1, 2, 3) | {a: {$in: [1, 2, 3]}} |
| 同时满足子文档条件 | {$elemMatch: {city: "Rome", country: "USA"}} |
常用更新操作符
| 操作 | 含义 |
|---|---|
$set / $unset | 设置 / 移除字段 |
$push / $pop | 数组追加 / 弹出 |
$pull / $pullAll | 按匹配从数组中删除 |
$addToSet | 不存在则添加,去重 |
08 聚合查询
Aggregation Framework:
pipeline = [stage1, stage2, ...]
db.<collection>.aggregate(pipeline, { option })
常用 stage:
| Stage | 作用 |
|---|---|
$match | 过滤 |
$project | 投影 |
$sort | 排序 |
$group | 分组 |
$skip / $limit | 分页 |
$lookup | 左外连接 |
09 聚合查询实验
// 全集合总计
db.orders.aggregate([
{ $group: { _id: null, total: { $sum: "$total" } } }
])
// { _id: null, total: NumberDecimal("44019609") }
// 2019 Q1 已完成订单总金额(订单金额 + 运费)和订单数
db.orders.aggregate([
{
$match: {
status: "completed",
orderDate: { $gte: ISODate("2019-01-01"), $lt: ISODate("2019-04-01") }
}
},
{
$group: {
_id: null,
total: { $sum: "$total" },
shippingFee: { $sum: "$shippingFee" },
count: { $sum: 1 }
}
},
{
$project: {
grandTotal: { $add: ["$total", "$shippingFee"] },
count: 1,
_id: 0
}
}
])
// { count: 5875, grandTotal: NumberDecimal("2636376") }
10 复制集机制及原理
角色
| 节点 | 数量 | 作用 |
|---|---|---|
| PRIMARY | 1 | 接收写入、参与选举投票 |
| SECONDARY | 2+(建议奇数) | 复制主节点新数据、参与投票 |
集群上限 50 节点,有投票权的节点最多 7 个。
数据复制
- 主节点把写操作(经必要转换)写入 oplog。
- 从节点通过在主节点上打开 tailable cursor 不断拉 oplog,本地回放。
故障恢复(选举)
- 有投票权的节点两两心跳。
- 连续 5 次心跳未收到判定失联。
- 主节点失联 → 触发选举;从节点失联 → 不选举。
- 基于 RAFT 一致性算法,需 多数投票节点存活。
当选条件
- 与多数节点建立连接。
- oplog 足够新。
- 优先级足够高(若有配置)。
节点选配项
| 参数 | 作用 | 注意 |
|---|---|---|
v | 是否具有投票权 | |
priority | 优先级(越高越优先成为 primary) | priority = 0 永不成为主节点 |
hidden | 复制数据但对应用不可见 | 可投票,但 priority 必须为 0 |
slaveDelay | 复制 N 秒前的数据(延迟节点) |
注意事项
- 硬件:节点配置必须一致(地位对等);不同节点的硬件需相互独立(避免同时宕)。
- 软件:所有节点版本必须一致。
- 增加节点 不会 提升写性能。
11 搭建 MongoDB 复制集
mkdir -p runtime/data_db{1,2,3} \
&& mkdir -p runtime/data_configdb{1,2,3}
hostname -f
rs.status()
12 全家桶
13 模型设计基础
三要素:Entity(实体)/ Attribute(属性)/ Relationship(关系)。
三层模型映射:
| 层次 | 概念 | 对应内容 |
|---|---|---|
| 概念 CDM | 业务对象 | 对象 |
| 逻辑 LDM | 实体 / 属性 / 关系 | 实体、属性、关系 |
| 物理 PDM | 数据库结构 | 表结构、字段列表、主外键 |
14 JSON 文档模型设计
无模式:物理建模过程可省。
设计原则:
- Performance 性能
- Ease of Development 开发易用
15 基础设计
流程:业务需求 / 逻辑模型 ──逻辑导向──→ 基础建模 → 集合 / 字段 / 基础形状 → 引用及关联 → 最终模式。
约束:
- 单文档最大 16 MB。
- 默认 内嵌为主。
16 工况细化
流程:技术需求 / 读写比例 ──技术导向──→ 工况细化 → 引用及关联。
引用模式 $lookup:
db.contacts.aggregate([
{ $lookup: {
from: "groups",
localField: "groups_ids",
foreignField: "groups_id",
as: "groups"
}}
])
何时改用引用
- 内嵌文档太大。
- 内嵌文档或数组元素频繁修改。
- 内嵌数组元素持续增长且无封顶。
引用方式的限制
- 没有主外键约束检查。
$lookup只支持 left outer join。$lookup的from不能是分片表。
17 模式套用
流程:经验 / 学习 ──模式导向──→ 套用设计模式 → 优化的模型。
分桶设计(Bucket Pattern):时序数据按时间段聚合到一个文档内。
- 大幅减少文档总数。
- 大幅减少索引占用空间。
18 设计模式集锦
| 场景 | 模式 | 做法 |
|---|---|---|
| 字段繁多、索引爆炸(多语言多国家属性) | 列转行 | 把列转成数组元素,一条索引解决所有该类查询 |
| 模式持续演进,需管理不同版本 | Schema Version | 文档加 schema_version 字段 |
| 网页点击流量等粗粒度统计 | 近似计算 | if random(0,9) == 0 increment by 10 |
| 业绩 / 游戏排名等精确统计 | 预聚合 | 模型中加统计字段,每次写入时同步更新 |
19 写操作事务 writeConcern
{ w: <value>, j: <boolean>, wtimeout: <number> }
w 取值:
1:写入主节点即返回。- 数字
n:等待 n 个节点确认。 "majority":等待多数节点确认。
db.test.insert({ count: 2 }, { writeConcern: { w: "majority" } })
db.test.insert({ count: 2 }, { writeConcern: { w: 3 } })
db.test.insert({ count: 2 }, { writeConcern: { w: 4 } })
// [Error] 100 - Not enough data-bearing nodes
实验:延迟节点 + writeConcern 超时
// 配置延迟节点(5s 延迟、不参选)
conf = rs.conf()
conf.members[2].slaveDelay = 5
conf.members[2].priority = 0
rs.reconfig(conf)
db.test.insert({ count: 2 }, { writeConcern: { w: 3 } })
// → 等待 5s 才返回
db.test.insert({ count: 5 }, { writeConcern: { w: 3, wtimeout: 3000 } })
// → writeConcernError: waiting for replication timed out
20 读操作事务 readPreference
参考:
三种配置方式:
| 方式 | 写法 |
|---|---|
| 连接串 | mongodb://.../?replicaSet=rs&readPreference=secondary |
| 驱动 API | MongoCollection.withReadPreference(ReadPreference readPref) |
| Mongo Shell | db.collection.find({}).readPref("secondary") |
实验:从节点写锁后读结果差异
// 主节点
db.test.insert({ count: 2 }, { writeConcern: { w: 3 } }) // 三副本确认
db.test.find({}).readPref("secondary") // 1 line
// 从节点 1、2
db.fsyncLock() // 锁住写入
// 主节点
db.test.insert({ count: 3 }) // 只写主
db.test.find({}) // 2 lines
db.test.find().readPref("secondary") // 1 line ← 从未同步
// 从节点 1、2
db.fsyncUnlock()
// 主节点
db.test.find().readPref("secondary") // 2 lines ← 已同步
21 读操作事务 readConcern
开启:enableMajorityReadConcern: true
// 从节点 1、2 锁定写入,模拟主从不一致
db.fsyncLock()
// 主节点
db.test.insert({ count: 3 })
db.test.find().readConcern("local") // 可能脏读(若该写操作后被回滚)
db.test.find().readConcern("majority") // 避免脏读
majority 近似 SQL 的 Read Committed。
22 多文档事务
ACID 在 MongoDB 的实现
| 性质 | 实现机制 |
|---|---|
| Atomicity | 事务本身 |
| Consistency | writeConcern + readConcern |
| Isolation | readConcern |
| Durability | Journal + Replication |
约束:
- 事务默认 60s 内必须完成。
- 多文档事务中的读 必须走主节点。
实验:事务隔离性
db.tx.drop();
db.tx.insertMany([{ x: 1 }, { x: 2 }]);
var session = db.getMongo().startSession();
session.startTransaction();
var coll = session.getDatabase('test').getCollection('tx');
coll.updateOne({ x: 2 }, { $set: { y: 1 } });
coll.find(); // 事务内:{x:1}, {x:2, y:1}
db.tx.find(); // 事务外:{x:1}, {x:2} ← 隔离
session.commitTransaction();
db.tx.find(); // {x:1}, {x:2, y:1}
实验:可重复读(snapshot)
db.tx.drop();
db.tx.insertMany([{ x: 1 }, { x: 2 }]);
var session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
var coll = session.getDatabase('test').getCollection('tx');
coll.findOne({ x: 1 }); // 首次读
// 事务外的更新
db.tx.updateOne({ x: 1 }, { $set: { y: 1 } });
coll.findOne({ x: 1 }); // 仍是首次的快照
session.abortTransaction();
实验:写冲突
// shell 1、2 同时开事务
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// shell 1
coll.updateOne({ x: 1 }, { $set: { y: 1 } }); // ok
// shell 2
coll.updateOne({ x: 1 }, { $set: { y: 1 } });
// MongoServerError: WriteConflict — 须 retry 或 abort
23 Change Stream
类似数据库触发器,基于 oplog。
对比传统触发器
| 维度 | 触发器 | Change Stream |
|---|---|---|
| 触发方式 | 同步(事务保证) | 异步 |
| 触发位置 | 数据库内 | 客户端回调事件 |
| 触发次数 | 一次 | 每个订阅事件的客户端各一次 |
| 故障恢复 | 事务回滚 | 从断点重新触发 |
可追踪事件
insert/update/deletedrop/rename/dropDatabaseinvalidate:drop / rename / dropDatabase触发,并关闭 change stream
约束
- 隔离级别 = 可重复读。
- 集群必须开启 majority readConcern。
- 集群无法满足
{w: "majority"}时不会触发。 - 可用聚合管道过滤事件。
25 分片集群机制及原理
进程角色
| 角色 | 进程 | 作用 |
|---|---|---|
| 路由 | mongos | 转发请求 |
| 配置 | mongod | 元数据 / 路由表 |
| 数据 | mongod | 实际存数据片段 |
数据分布策略
| 策略 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|
| 范围 | 范围查询快 | 数据可能倾斜 | 自然有序的数据 |
| 哈希 | 分布均匀 | 范围查询差 | 日志、物联网 |
| Zone | 天然分区 | 维护成本高 | 按地区 / 业务隔离 |
26 分片集群设计
核心术语:
| 术语 | 含义 |
|---|---|
| shard key | 文档中作为分片依据的一个字段 |
| doc | 文档 |
| chunk | 一个 shard key 范围内的文档块 |
| shard | 分片节点(一个 replica set) |
| cluster | 整个分片集群 |
片键设计三条
- 取值基数范围要大。
- 取值范围尽可能均匀。
- 对主要查询要具有 定向能力。
组合片键 例:{user_id: 1, time: 1}。
27 分片集群搭建及扩容
# 1 配置域名解析
# 2 准备分片目录
# 3 创建第 1 个分片复制集 member1:27010 member3:27010 member5:27010
mongod --bind_ip_all --replSet shard1 --shardsvr --wiredTigerCacheSizeGB 1
# --shardsvr 标注为分片节点
# --wiredTigerCacheSizeGB 缓存大小
# 4 初始化分片复制集
# 5 创建 config server 复制集 member1:27019 member3:27019 member5:27019
mongod --bind_ip_all --replSet config --configsvr --wiredTigerCacheSizeGB 1
# --configsvr 标注为配置节点
# 6 初始化 config server 复制集
# 7 搭建 mongos
mongos --bind_ip_all --configdb config/member1:27019,member3:27019,member5:27019
# 连接 mongos 添加分片
sh.addShard("shard1/member1:27010,member3:27010,member5:27010")
# 8 创建分片表
sh.enableSharding("foo");
sh.shardCollection("foo.bar", {_id: "hashed"});
sh.status();
# 插入测试数据
use foo
for (var i = 0; i < 10000; i++) {
db.bar.insert({ i: i })
}
# 9~11 创建并加入第 2 个分片复制集
sh.addShard("shard2/member2:27011,member4:27011,member6:27011")
28 监控最佳实践
工具:
- MongoDB Ops Manager
- Percona
- 自研监控脚本
数据获取:
db.serverStatus() // 从上次启动到现在
db.serverStatus().opcounters
db.isMaster()
mongostat // CLI 工具
29 备份与恢复
策略:
- 延迟节点 —— 抗误删。
- 全量 + Oplog 增量 —— oplog 幂等可重放。
mongodump --oplog不遗漏 dump 期间的数据。mongorestore --oplogReplay配合恢复。
31 安全架构
db.createRole({
role: "readWriteRole",
privileges: [{
resource: { db: "myDatabase", collection: "sample" },
actions: [ "find", "insert", "update", "remove" ]
}],
roles: [{ role: 'read', db: 'sampledb' }]
})
db.createUser({
user: 'sampleUser',
pwd: 'pwd',
roles: [{ role: 'readWriteRole', db: 'admin' }]
})
32 安全加固实践
mongod --noscripting # 禁止脚本执行
mongod --auth # 强制鉴权
33 索引机制(一)
核心概念速查:
| 概念 | 含义 |
|---|---|
| Index / Key / DataPage | 索引、键、数据页 |
| Covered Query / FETCH | 完全由索引返回 / 需要回表取数据 |
| IXSCAN / COLLSCAN | 索引扫描 / 集合扫描 |
| Big O Notation | 时间复杂度 |
| Query Shape | 查询形状(相同 shape 可复用执行计划) |
| Index Prefix | 索引前缀 |
| Selectivity | 选择性 —— 过滤掉的比例越大越好 |
B-Tree vs B+Tree
| 维度 | B-Tree | B+Tree |
|---|---|---|
| 数据存放 | 所有节点都可存 key + value | 仅叶子节点存 key + value |
| 叶子链表 | 无 | 叶子用指针串成有序链表,区间查询友好 |
| 适用 | 内存索引、缓存 | 磁盘索引(数据库、文件系统) |
MongoDB / MySQL InnoDB 都用 B+Tree。
34 索引机制(二)
.explain(true) 关注字段:
| 字段 | 含义 |
|---|---|
executionTimeMillis | 执行毫秒数 |
totalDocsExamined | 共扫描文档数 |
executionStages.docsExamined | 当前阶段扫描文档数 |
executionStages.inputStage.stage | 输入阶段类型 |
ESR 原则:组合索引顺序 = Equal(最前)→ Sort(中间)→ Range(最后)。
后台建索引:
db.member.createIndex({ city: 1 }, { background: true })
36 性能诊断工具
mongostat —— 实时运行状态:
| 指标 | 关注阈值 |
|---|---|
| dirty | < 5%(脏页比例) |
| used | 缓存使用率 |
| qrw | 排队请求数 |
| con | 连接数 |
mongotop —— 集合维度压力。
mtools —— 日志分析。
41 应用场景及选型
优点:
- 横向扩展 —— 数据 / 并发增长可自动扩容。
- 灵活模型 —— 适合快速迭代、数据模型多变。
- JSON 结构 —— 与微服务 / REST API 天然契合。
44 关系型数据库迁移
迁移到 MongoDB 的理由:
- 高并发(数千 ~ 十万 ops),关系型不易扩展。
- 快速迭代,关系型模式太严谨。
- 灵活的 JSON 模式。
- 大数据量需求。
- 地理位置查询。
- 多数据中心跨地域部署。
References
– EOF –