MongoDB 核心考点速记笔记(锚定实习 RBAC 版本模型代码)
定位:写在 za-lincoln 技术栈里必被问。目标------概念题秒答,且每题都能落到"我实习怎么用的"。
一、MongoDB 是什么 & 与 MySQL/Redis 选型(必考第一问)
-
定位: 面向文档的分布式数据库(BSON 文档 = 二进制 JSON),Schema 灵活(同集合文档结构可不同),原生支持水平扩展(分片)与副本集高可用。
-
三选型一句话:
- MySQL: 关系强、多表 JOIN、事务强一致(钱、账)→ 结构化核心数据。
- MongoDB: 写多读少或结构多变、嵌套层次深、聚合统计(日志、配置版本、画像、内容)→ 免 JOIN 的对象型数据。
- Redis: 纯缓存/高频读写,数据可丢或可重建。
-
实习锚点: RBAC 权限版本(
roles是嵌套 Map:角色→资源→操作三层嵌套)------关系库要拆 5 张表 + 5 次 JOIN,MongoDB 一个文档一次读出,这就是选它的理由(面试原话)。
二、文档模型核心概念(对齐 MySQL 词汇表)
| MySQL | MongoDB |
|---|---|
| Database | Database |
| Table | Collection |
| Row | Document(BSON) |
| Column | Field |
| 主键 | _id(默认 ObjectId:12 字节 = 4 时间戳 + 5 机器随机 + 3 计数器) |
| JOIN | 嵌入式文档 (或 $lookup,性能差少用) |
- 嵌套 vs 引用(经典设计题): "一起读、强从属" → 嵌入( RBAC 的 roles);"独立生命周期/多对多" → 引用(分集合 + 应用层组装)。
- 反范式陷阱: 嵌入文档无限膨胀(16MB 上限)→ 大数组要拆出集合。
三、必备查询与更新语法(写过的才敢说熟)
-
查询:
db.col.find({age: {$gt: 18}}, {name: 1});$in/$ne/$exists;数组匹配是"包含"语义 ({tags: "a"}匹配数组含 a 的文档)。 -
投影: 第二个参数选字段(
_id默认带,_id: 0关掉)。 -
更新三兄弟(辨析必考):
updateOne/Many+$set(只改指定字段;不写$set会整文档替换------新手大坑)。$setOnInsert: 只在 upsert 插入新文档时生效,已有文档不动------RBAC 草稿创建用它保护已存在草稿的字段不被覆盖。$inc: 原子自增(计数器场景)。
-
upsert: 查到则更新、没查到则插入(
upsert: true)------必须配唯一索引 防并发双插(与 MySQLON DUPLICATE KEY UPDATE同思想,见项目篇-1)。 -
findAndModify(实习核心武器): "查找+修改+返回修改后文档"一条原子命令 。RBAC 的
getOrCreateDraftRole用findAndModify + upsert + returnNew实现"存在草稿就返回、没有就创建"------不加锁天然幂等 ,等价于"带返回值的原子 upsert"(比update + 再查一次的两步竞态安全)。
四、索引与执行计划
- 索引类型: 单字段、复合索引 (同样遵循最左前缀)、唯一索引 (防重)、TTL 索引(到期自动删文档,日志/会话场景)、文本/Atlas 全文索引(一般交给 ES,别硬扛)。
- 索引底层: B+ 树(和 MySQL 一样,3.0+ WiredTiger 引擎;早期 MMAPv1 是 B 树------说"Mongodb 是 B 树"是老黄历)。
- explain:
db.col.find(...).explain("executionStats"),看totalDocsExaminedvsnReturned(扫描/返回比 = 索引效率)。 - 慢查询三板斧: profiler(
db.setProfilingLevel(1, {slowms: 100}))→ explain 看是否全扫 → 补索引/改查询。
五、事务与一致性(高频追问)
- 单文档原子性: 单文档的更新(含嵌套数组操作、findAndModify)天然原子------多数场景根本不需要多文档事务(这是 MongoDB 的设计哲学,先说这句加分)。
- 多文档事务: 4.0 起副本集支持、4.2 起分片支持,用法
session.startTransaction()。代价: 性能差(跨节点协调)、默认 60s 超时------能用单文档原子 + 应用层补偿就不用多文档事务。 - 实习锚点: RBAC 版本激活"先置新 VALID 再废旧 VALID"两步用 BulkWrite 一次提交 ,且顺序故意反着来 :先废后立会出现"无 VALID 版本"窗口(查询报错),先立后废只是"暂时双 VALID"(查询仍能返回)------用顺序设计规避分布式事务,面试讲出来就是设计功底。
- 写关注(Write Concern):
w:1(主节点确认,默认)/w:majority(过半确认,防主从切换丢写)/w:0(不等确认,最快)。要求不丢写 → majority + journal。
六、副本集与分片(架构层必考)
1. 副本集(Replica Set)------ 高可用
- 角色: 1 Primary(唯一写入口)+ N Secondary(同步复制 oplog)。
- 选举: Sentinel 式?不------基于 Raft 思想 的自身协议,多数派投票选出新主(默认约 10s 心跳超时,任期 term 防脑裂)。类比记忆:MySQL 主从 + Raft 选举的合体。
- Secondary 三种形态: 普通备份 / Priority 0 (永不参选)/ Hidden(对客户端不可见,专给报表)。
- 读写分离:
readPreference=secondaryPreferred读从库------接受最终一致,写永远走主。 - oplog: 主库的操作日志(幂等可重放的幂等性 op),从库拉取回放------类比 MySQL binlog(但 oplog 是幂等格式,重放安全)。
2. 分片(Sharding)------ 水平扩展
- 三大组件: mongos(路由,无状态)+ Config Server(元数据,副本集)+ Shard(数据片,各自是副本集)。
- 分片键(Shard Key): 决定文档落哪个片。必须建在分片键上的查询才只路由到单片(否则广播全片)。
- 两类策略: 范围分片 (range,易理解但易热点------单调递增键全打到最后一片)vs 哈希分片(hash,均匀但牺牲范围查询)。默认哈希更常用。
- vs 手动分库分表(MySQL): Mongo 分片是数据库原生自动的(应用无感知、自动均衡 rebalance),MySQL 要中间件/应用层搞------这就是"结构多变 + 需要水平扩展"场景选 Mongo 的第二个理由。
- jumbo chunk / 热点: 单个分片键值过大无法分裂 → 选高基数(取值多)的分片键。
七、存储引擎与内存(WiredTiger)
- WiredTiger(3.2+ 默认): 文档级并发控制(读写锁粒度到文档,高并发写入强)+ 快照隔离 MVCC + B+ 树索引。
- 缓存: 默认约
(RAM - 1GB) / 2为 WiredTiger Cache------MongoDB 吃内存是设计使然(热数据全在 cache 才快),生产独占机器、别和 MySQL 挤一台。 - 日志: journal(WAL 预写日志,崩溃恢复,类比 InnoDB redo log)+ checkpoint。
八、Aggregation 聚合管道(替代复杂 SQL 的武器)
- 管道思想:
$match → $group → $sort → $limit一条管道流式处理,等价于 SQL 的 WHERE/GROUP BY/ORDER BY。 - 高频算子:
$match(尽量放最前面,先过滤少过数据)、$group+$sum/$avg、$project(投影)、$unwind(数组炸开成多行)、$lookup(左连接,能不用就不用)。 - 实战锚点: RBAC 的"角色数量、权限覆盖度统计"(findAllApiResources 后算覆盖)就是聚合管道------和 SQL 对着讲("把 GROUP BY 换成 $group 而已")。
九、实习项目深挖预案(背熟这三问三答)
-
"为什么 RBAC 用 MongoDB 不用 MySQL?"
"三层嵌套结构(角色→资源→操作)在关系库要拆 5 张表 + 5 次 JOIN,读一个完整版本要拼装;Mongo 一个文档整存整取,且版本快照这种'写少读多、按版本整体读取'的模式和文档模型天然匹配。事务弱点的风险我们用单文档原子操作 + 顺序设计(先立后废)规避了。"
-
"并发发布两个草稿怎么办?"
"Redis 分布式锁 + 锁内双重校验 + Mongo 版本号自增;草稿获取本身用 findAndModify+upsert 原子幂等,连锁都不用。"
-
"MongoDB 事务了解吗?"
"单文档天然原子;多文档 4.0+ 支持但我们没用------BulkWrite 一次提交两步更新 + 反序设计替代了它,性能更好。"
十、自测清单
- 文档 vs 关系选型的三个判据?ObjectId 的组成?
findAndModify + upsert为什么是原子的?$setOnInsert干嘛的?- 先废后立 vs 先立后废的窗口差异?BulkWrite 是原子的吗?
- 副本集选举基于什么思想?oplog 类比 MySQL 什么?为什么是幂等的?
- 分片键两类策略的取舍?什么查询会广播?
- WiredTiger 的并发控制粒度?为什么 Mongo 吃内存是正常的?