MongoDB FAQ
内容:基础、索引、事务、性能优化、复制集、分片、场景题,按照难度从初级到高级排列。
MongoDB 是一个开源、分布式、面向文档的 NoSQL 数据库,采用 BSON 格式存储数据。
核心特点:
- 无模式(Schema-Free)
- 文档模型(支持嵌套结构)
- 高性能、高并发
- 原生支持横向扩展(Sharding)
- 自动故障转移(Replica Set)
- 丰富查询(包括聚合、地理位置、全文索引)
MongoDB 使用 文档(Document) 作为最小存储单位,相当于关系型数据库的一行。
- 文档 -> BSON
- 集合(Collection) -> 表
- 数据库(Database) -> 数据库
BSON = Binary JSON
相比 JSON:
- 支持更多数据类型(date、binary、decimal128)
- 读取快
- 有长度字段,可顺序扫描
常见索引类型:
- 单字段索引
- 复合索引(Compound)
- 唯一索引(unique)
- TTL 索引
- 哈希索引(Hashed)
- 文本索引(Text)
- 地理位置索引(2dsphere)
如索引:
{ a: 1, b: 1, c: 1 }
可用于:
- a
- a + b
- a + b + c
不能用于:
- b
- c
- b + c
COLLSCAN = 全表扫描
表示没有走索引,是性能问题的最大根源。
使用:
db.collection.find({...}).explain("executionStats")
关注字段:
- stage 是否包含 IXSCAN
- totalDocsExamined 是否远小于 nReturned
是的。
MongoDB 只能在 索引顺序完全匹配 的情况下进行排序。
支持。
从 MongoDB 4.0 开始支持 多文档 ACID 事务(Replica Set)
从 4.2 起支持 分片集群事务(Sharding)
MongoDB 的事务性能更差,因为其文档模型原本就设计为减少事务场景。
MongoDB 事务适用于:
- 多集合跨文档一致性
- 金融级业务极少使用的场景
- 如果不存在 -> 插入
- 如果存在 -> 更新
相当于 MySQL 的:
insert ... on duplicate key update
当文档大小变大时,需要移动到新的数据页,会导致:
- 性能降低
- 磁盘 I/O 增加
- 复制集 oplog 增大
避免方法:
- 不要频繁增加/减少文档字段
- 大字段拆分子集合
常见 TOP 原因:
- 无索引 -> COLLSCAN(最常见)
- 复合索引未命中
- 查询返回字段过多(未使用投影)
- 写入太频繁导致锁
- 文档太大(>16MB 限制)
- COUNT() 过慢
- 事务长时间占用锁
- 磁盘 I/O 高
不要使用:
skip + limit
因为 skip 需要扫描 N 条记录。
使用:
{_id: {$gt: last_id}} // “游标分页”
limit 10
使用:
bulkWrite()
而不是循环 insert。
因为 MongoDB 写入需要:
- 写 journal
- 写数据文件
- 复制到副本集其他节点
- 更新多个索引
写放大显著比 MySQL 大。
当聚合管道需要大量内存:
db.col.aggregate([...], { allowDiskUse: true })
基于 Raft 类似机制。
选举条件:
- 成员 ping 可达
- 票数过半
- 优先级优先(priority)
- oplog 数据最新
原因:
- 网络不通
- 心跳超时
- 优先级低
- oplog 追不上
- 资源不足(内存/CPU 过高)
oplog(operation log)是 复制集的数据复制日志。
SECONDARY 节点从 oplog 同步数据。
关键参数:
- oplog 大小(要保证至少 24 小时窗口)
- oplog 落后太久会导致需要全量复制
rs.printSlaveReplicationInfo()
因为单个节点:
- 内存有限
- 磁盘有限
- IOPS 不够
- QPS 负载瓶颈
分片可横向扩容至无限容量。
最佳实践:
- 高基数(high cardinality)
- 写入均匀
- 避免单调递增
推荐:
- hashed(_id)
- hashed(user_id)
典型问题:
- 数据热点(所有写入集中到一个分片)
- chunk 分裂/迁移速度慢
- 节点负载不均衡
不能修改(几乎不可能)。
MongoDB 设计中 shard key 是永久性的。
mongod.conf:
security:
authorization: enabled
SCRAM-SHA-256
比 SHA-1 更安全。
基于:
- 用户(user)
- 角色(role)
- 数据库(db)
例如:
readWrite
dbAdmin
clusterAdmin
backup
restore
排查思路:
- currentOp() 查看哪些查询在跑
- explain() 是否出现 COLLSCAN
- 看是否走了全文索引
- 观察 WiredTiger cache
- 观察是否大量 fetch 文档
- 看是否有大 $in 查询
- 是否有无索引的 $lookup
- 是否有长事务卡住锁
可能原因:
- 索引太多 -> 写入变慢
- journal 写入慢(磁盘 I/O)
- 文档过大
- 分片平衡导致 chunk migrate 阻塞
- 复制集延迟大
- 主节点锁等待
排查:
- db.serverStatus()
- iostat
- rs.printSlaveReplicationInfo()
策略:
- 创建必要索引
- 热点字段分片
- 使用压缩
- 不要查询大文档
- 禁止 deep skip
- 使用游标分页
- 定期 archive 归档旧数据
- 将大字段(如图片)拆分存储到 GridFS
运行:
sh.status()
检查:
- chunk 分布
- 每个 shard 的数据量差异
- balancer 是否运行
解决方案:
- 新建一个新的 collection -> 正确 shard key
- 导入数据 -> 落新 collection
- 逐渐切换业务到新的 collection
MongoDB 不支持直接修改 shard key。
反例:
- 文档太大
- 太多嵌套结构
- 数组无限增长
- 单文档单字段过巨大(>16MB)
- 子文档更新过多 -> document move
WiredTiger Cache 控制 MongoDB 内存分配(50% 内存)。
作用:
- 存放热数据
- 存放索引
- 读写缓存
Cache miss 会:
- 触发磁盘 I/O
- 性能急剧下降
- 文档级锁(写入锁单文档,不锁集合)
- 全局锁(元数据操作)
- 读写锁(Share/Exclusive)
- 索引创建时可能全局锁
解决方法:
- hashed shard key
- 拆分成多个集合(冷热分离)
- 增加多个 mongos
- 减少热点字段更新
通过:
- Write Concern
- Read Concern
- Multi-Document Transactions
- Majority Quorum
控制写入确认级别:
- 0 fire-and-forget
- 1 primary
- majority(推荐)
- w: <num> n 个节点确认
工具:
- mongodump/mongorestore
- mongoexport
- 全量物理复制(LVM snapshot)
- 复制集拉数据