MongoDB插入/更新查询性能下降及CPU占满原因咨询
这是个非常典型的MongoDB数据量到达阈值后触发的性能瓶颈问题,结合你描述的「特定文档数后CPU打满、慢操作集中在写和findAndModify」的现象,我来梳理几个最可能的根因和对应的排查/解决思路:
1. 索引缺失或低效,导致全集合扫描
update、findAndModify这类操作需要先定位目标文档,如果你的查询/更新条件没有对应的索引,当集合数据量达到4万、8万这个量级后,每次操作都会触发全集合扫描(COLLSCAN)——这会让CPU在遍历大量文档上消耗巨量资源,直接打满CPU。
比如handHistory集合到8万才出问题,大概率是这个集合的更新/查询条件没建索引,或者索引的选择性太差。你可以:
- 用
db.handHistory.explain("executionStats").find({/* 你的更新条件 */})查看执行计划,确认是否出现COLLSCAN; - 打开Mongo Compass的慢查询面板,查看这些慢操作的
executionStats,重点看totalDocsExamined是否远大于nReturned。
解决思路:给常用的查询/更新字段建立合适的索引,比如针对findAndModify的查询条件建单键或复合索引;避免在高基数字段(比如UUID)建索引,同时定期清理无用索引减少写开销。
2. 内存缓存不足,触发磁盘交换
MongoDB的WiredTiger引擎严重依赖内存缓存来存储热数据和索引。当三个集合的总数据量+索引大小超过服务器可用内存(尤其是WiredTiger的缓存上限),系统会开始频繁把内存数据交换到磁盘(swap),这会导致CPU在处理磁盘IO的调度、数据序列化上消耗大量资源,最终打满CPU。
你可以排查:
- 执行
db.serverStatus().wiredTiger.cache,查看cache hit ratio(缓存命中率),如果低于95%说明缓存不足;同时看page eviction(页面驱逐)的频率,数值过高说明内存不够用; - 查看系统的swap使用情况(比如
free -h),如果swap被大量占用,基本可以确认是内存不足导致的交换。
解决思路:如果服务器还有空闲内存,调大WiredTiger的缓存大小(修改mongod.conf里的storage.wiredTiger.engineConfig.cacheSizeGB,建议设为服务器内存的60%-70%,留足系统内存);如果内存已经耗尽,考虑升级服务器内存,或者对冷数据做归档(比如把旧的handHistory数据迁移到归档集合)。
3. 锁竞争加剧,CPU消耗在上下文切换
虽然MongoDB 3.0+用的是文档级锁,但如果你的写操作(尤其是findAndModify这种原子操作)集中在同一个文档、同一个索引键范围,会导致严重的锁竞争。大量请求在等待锁的过程中,CPU会频繁做上下文切换,最终占满CPU资源。
你可以排查:
- 执行
db.currentOp(),查看是否有大量waitingForLock: true的操作,以及锁的类型(比如write锁); - 查看
db.serverStatus().locks里的acquireCount和waitCount,如果waitCount远大于acquireCount,说明锁竞争严重。
解决思路:优化业务逻辑,避免大量请求同时操作同一个文档;如果是批量更新,拆分请求分散到不同的文档范围;对于findAndModify操作,尽量缩小查询条件的范围,减少锁持有时间。
4. WiredTiger页面拆分与写放大
WiredTiger的存储结构是基于页的,如果你的文档是动态增长的(比如handHistory里的操作日志数组不断变长),或者插入的文档分布不均匀,当数据量增大后会频繁触发页面拆分(page split)——这个过程需要CPU来重新组织页面数据,同时会导致写放大(实际写入磁盘的数据远大于你更新的数据),最终消耗大量CPU。
你可以排查:
- 执行
db.serverStatus().wiredTiger.transaction,查看page splits的数值,如果增长很快说明页面拆分频繁; - 查看集合的
db.handHistory.stats(),计算storageSize / dataSize的比值,如果比值超过2,说明存在严重的写放大或数据碎片。
解决思路:如果是文档动态增长导致的,尽量预先分配文档空间,或者把变长的子文档拆分到单独的集合;对于分布不均匀的插入,考虑用哈希索引替代范围索引,减少页面拆分;定期整理集合碎片(低峰期执行db.collection.reIndex()或db.runCommand({compact: "handHistory"}))。
5. 不合理的配置参数或附加功能
比如:
- WiredTiger的缓存设置过低,默认是内存的一半(最多10GB),如果服务器内存充足但没调大,会浪费内存导致缓存不足;
- 开启了不必要的TTL索引、全文索引,这些索引在数据量增大后会频繁后台扫描,消耗CPU;
- 副本集的同步压力过大,如果你的生产环境是副本集,当主节点写操作量大时,从节点的同步过程会消耗额外的CPU资源。
解决思路:检查mongod.conf里的配置,调整缓存、索引相关参数;关闭不必要的附加索引;如果是副本集,确保从节点的硬件配置足够,或者调整同步策略。
内容的提问来源于stack exchange,提问作者Digvijay Rathore

