MongoDB常见面试题总结(上)
MongoDB 面试的上半场围绕四件事展开:它为什么存在(文档模型与 BSON)、数据怎么落盘(WiredTiger 引擎)、宕机怎么活(副本集与 oplog)、多文档操作靠什么兜底(事务)。把这条主线串起来,比零散背题有效得多。
考察地图
- 定位与模型:MongoDB 是什么、BSON 与 JSON 的区别、适用与不适用场景。考察的是选型判断力。
- 存储引擎:引擎有哪些、WiredTiger 用 B+ 树还是 LSM、缓存与压缩。考察对数据落盘方式的理解。
- 高可用:副本集选举、oplog、writeConcern。考察「写入如何不丢」这条链路。
- 事务:从单文档原子性到多文档事务的演进与边界。
- 建议自行查阅:CRUD 语法、驱动连接方式——纯 API 记忆,面试很少单独成题。
定位与模型
MongoDB 是什么?与关系型数据库差在哪?
结论:MongoDB 是面向文档的 NoSQL 数据库,以 BSON 文档为基本数据单元、集合对应关系库的表、不要求预先定义结构,并原生支持副本集高可用与分片水平扩展。
原理:术语对照一目了然:表→集合,行→文档,列→字段,主键→_id(默认 ObjectId)。关系模型要求表结构先行、同列类型统一;文档模型把一个业务对象(含嵌套数组与子文档)整体存为一条记录,同一集合内的文档结构允许不同。代价是跨集合关联能力弱——没有 JOIN,只能靠 $lookup 或应用层拼装。
延伸:「无模式」不等于「无设计」。生产环境仍要约定字段与结构版本,必要时用 schema validation 兜底,否则字段漂移之后,查询与索引设计都会失控。
追问:文档模型为什么能省掉 JOIN?提示:把原本一对多的子表内嵌为文档内的数组,一次读取取回整个聚合。
BSON 与 JSON 有什么区别?为什么 MongoDB 用 BSON?
结论:BSON 是 JSON 的二进制扩展,增加了数据类型并带长度前缀,为存储与扫描效率而设计。
原理:差异有三点。其一,类型系统更全:JSON 只有字符串、数字、布尔、数组、对象、null,BSON 补充了 ObjectId、Date、64 位整数、Decimal128、二进制数据等。其二,BSON 文档中每个元素带长度前缀,解析器可以跳过不关心的字段直接定位目标,不必逐字符扫描文本;代价是同样的数据 BSON 占用空间通常略大于 JSON。其三,BSON 是二进制格式,省去文本解析开销,适合直接映射到内存结构遍历。
延伸:开发时写的查询仍是 JSON 风格语法,由驱动负责 JSON 语义与 BSON 编码之间的转换;真正落盘、进索引、进 oplog 的都是 BSON。
追问:单个 BSON 文档的大小上限是多少?提示:16MB,超出的大对象用 GridFS 分块存储。
MongoDB 适合哪些场景?哪些场景不适合?
结论:适合 schema 演进快、单对象内聚、需要水平扩展的业务;不适合跨实体强一致事务与复杂多表关联分析。
原理:典型适用面包括:内容与商品目录(字段随类目变化)、日志与事件流(高写入、结构松散)、用户画像与配置(嵌套结构一次取回)、物联网数据采集(写入量大、格式多变)。典型不适配面:资金账务这类必须跨实体强一致的转账——把多文档事务当主力会付出明显性能代价;需要大量 JOIN 与即席报表的分析型负载——应交给关系库或 OLAP 引擎。
延伸:实践中的常见组合是 MongoDB 管业务文档、MySQL 管交易流水、ES 管搜索,各取所长;跨库一致性用消息或 CDC 兜底,而非双写。
追问:「灵活 schema」会带来什么运维负担?提示:字段漂移、索引设计困难、历史脏数据,需要 schema 版本化与校验约束。
存储引擎
MongoDB 有哪些存储引擎?WiredTiger 凭什么成为默认?
结论:官方引擎主要是 WiredTiger(3.2 起默认)和企业版的 In-Memory;早期的 MMAPv1 已在 4.x 系列彻底移除。
原理:WiredTiger 相比 MMAPv1 的跃升在三点:并发粒度从集合级锁细化到文档级并发控制,同一集合内不同文档的写入可以并行;引入 checkpoint 加 journal 的崩溃恢复机制,不再需要漫长的修复过程;原生支持集合与索引压缩,默认 Snappy,可选 zlib、zstd。In-Memory 引擎把数据常驻内存换取可预测的低延迟,但掉电即失,只适合对延迟极端敏感且可重建的数据。
延伸:MongoDB 与 MySQL 一样是插件式存储引擎架构,Server 层与引擎层解耦,理论上允许第三方引擎接入——RocksDB 就曾以 MongoRocks 的形态存在过。
追问:文档级并发控制意味着什么?提示:写同一集合不再像 MMAPv1 那样互相阻塞整库或整集合。
WiredTiger 底层是 B+ 树还是 LSM 树?
结论:默认 B+ 树。WiredTiger 自身也提供 LSM 实现,但 MongoDB 并未启用这条路径。
原理:WiredTiger 以 page 为单位组织 B+ 树:root page 是根,internal page 只存键做路由,leaf page 存键值并携带页头、块头与 checksum。这与多数 NoSQL(HBase、Cassandra、RocksDB)选择 LSM 形成对照。可以这样理解取舍:MongoDB 主打通用 OLTP 文档负载,读多且随机,B+ 树读路径短而稳定;LSM 写吞吐高但读要穿透多层结构,更适合写密集的日志型负载。
延伸:「MongoDB 用 B 树」是流传甚广的错误说法——内部页不存数据、叶子页承载键值、范围扫描沿叶子层推进,这是标准的 B+ 树形态。
追问:B+ 树相对 B 树的两个优势是什么?提示:内部页扇出更大、树高更低;范围查询只需扫叶子层。
WiredTiger 的缓存与压缩是怎么回事?
结论:WiredTiger 维护独立缓存(默认约为物理内存的一半减 1GB),读命中它决定延迟;压缩用 CPU 换磁盘与缓存的有效容量。
原理:查询先在引擎缓存找 page,未命中才读磁盘;写入先修改缓存中的 page 并记录 journal,由 checkpoint 周期性地把脏页刷回数据文件。压缩方面:集合默认 Snappy 块压缩(压缩比约 3~5 倍),索引采用前缀压缩,journal 默认同样压缩(小于 128 字节的记录不压)。缓存中放的是解压后的数据,磁盘上是压缩后的,因此压缩同时放大了缓存能装下的有效数据量。
延伸:容量规划的核心是让 working set(热数据加热索引)留在缓存里;一旦超出,磁盘 I/O 会让延迟陡增。同时注意别把内存全分给引擎——读磁盘文件仍会经过操作系统的页缓存,两者需要留出平衡。
追问:会不会出现引擎缓存与 OS 页缓存的双重缓存问题?提示:会有一定程度的重复,但这是有意设计,分配内存时两边都要留。
副本集与高可用
副本集怎么工作?主节点挂了如何选出新主?
结论:副本集是一组维护相同数据的 mongod 进程:一个主节点接收全部写操作,若干从节点异步复制;主不可达时,多数派投票选出新主。
原理:一主两从(PSS)是最小生产形态。成员之间每两秒一次心跳,主节点超时不可达即触发选举;候选人必须拿到多数票,且数据不能太旧(比较各自最新 optime),以此保证选出的新主是集群里数据最全的节点。可以用 priority、votes 调整角色:priority 为 0 的节点永不参与竞选;arbiter 只投票不存数据,用来凑多数派。选举期间集群短暂不可写,驱动自动重连新主,对客户端基本透明。
延伸:两数据节点加一仲裁能省一台机器的成本,但牺牲了一份数据副本的容灾余量;跨机房部署时要保证任意单机房故障后,存活节点仍能凑出多数派。
追问:为什么不建议两节点副本集?提示:任一节点宕机都凑不出多数派,集群退化为只读。
oplog 是什么?为什么它是复制的生命线?
结论:oplog 是 local 库下一个有容量上限的集合(capped collection),记录主节点上所有改变数据的操作;从节点靠持续拉取并重放它来保持同步。
原理:capped collection 类似定长循环队列:顺序追加、写满后覆盖最老文档,写入快且无需维护索引。oplog 中每条操作都是幂等的——重放多次结果一致,因此从节点断线重连后可以从上次位置继续回放。oplog 的时间窗口(最老到最新操作的跨度)决定了从节点最多能离线多久:超过窗口就只能做全量初始同步。
延伸:Change Streams 与不少 CDC 工具正是消费 oplog 实现变更订阅的。写入压力大的集群要主动调大 oplog 容量,否则留给同步的容错空间很小。
追问:oplog 与 MySQL 的 binlog 有什么异同?提示:同为复制日志;区别在于 oplog 存的是幂等的操作语义,且是有界集合、会自动覆盖旧记录。
writeConcern 的 majority 到底防住了什么?
结论:writeConcern 决定写操作要在多少个节点上确认后才返回成功;w: "majority" 保证写入已复制到多数节点,从而主节点宕机也不会丢。
原理:默认 w: 1 只等主节点本地确认。考虑这个时序:写刚到主节点、尚未同步到任何从节点,主宕机,从节点被选为新主——那条写就随旧主的数据回滚(rollback)消失了。改成 majority 后,写必须到达多数节点才算成功,而选举新主同样要求多数派同意且数据最新,两个多数派必有交集,丢写窗口被关闭。代价是写延迟随复制链路变长。
延伸:读侧有对称的开关:readConcern: "majority" 只读取已被多数节点确认的数据,避免读到将来可能被回滚的「脏」写;readPreference 则决定读请求发给主还是从。
追问:majority 能否百分之百防丢?提示:前提是多数派存活;若多数节点同时损毁仍可能丢数据,这正是跨机房部署的意义。
事务
MongoDB 支持事务吗?边界在哪里?
结论:单文档操作天然原子;多文档事务自 4.0 起支持副本集、4.2 起支持分片集群,但都有明确的性能与使用边界。
原理:文档模型下,一个聚合内的多张「子表」内嵌在同一文档里,单文档原子写就覆盖了大部分原本需要事务的场景。多文档事务由 WiredTiger 的快照隔离支撑(引擎提供 read-uncommitted、read-committed、snapshot 三级),代价同样明显:事务有运行时长限制、对 oplog 体积形成压力,分片事务还要跨 shard 协调,吞吐与延迟都显著差于普通写入。
延伸:官方的态度很直接——能用嵌入建模消解的,就不要上多文档事务;真正跨聚合的资金类强一致需求,应当评估它是否本就该放在关系型数据库里。
追问:4.0 与 4.2 的事务差别是什么?提示:4.0 限定单个副本集内,4.2 扩展到分片集群,多文档事务从此也称分布式事务。

