主从切换后查询变慢的根本原因是索引未预热,新主节点内存中缺乏活跃索引页和查询计划缓存,导致频繁磁盘读取与重复计划生成;需避免误清计划缓存、合理配置WiredTiger缓存并模拟真实查询路径预热。

主从切换后查询变慢,根本原因是索引没“热”起来
新主节点刚接管时,内存中几乎没有活跃索引页和查询执行计划缓存,所有查询都要从磁盘读取索引块、重建 B-tree 节点路径,甚至反复解析相同 find() 语句。这不是配置错误,而是 MongoDB 的内存管理机制决定的——它不主动预加载索引,只按需载入。
db.collection.getPlanCache().clear() 会加剧冷启动延迟
有些运维脚本在切换后调用该命令清空查询计划缓存,本意是避免旧计划残留,结果反而让每个查询都重新生成执行计划(queryPlanner 阶段耗时显著上升)。尤其对带 $or、多字段排序或聚合管道的查询,计划生成开销可能超过实际执行。
- 只在确认计划严重劣化时才手动清理,日常切换无需干预
- 可通过
db.setProfilingLevel(1, { slowms: 50 })捕获executionStats.executionTimeMillis中 planGenerationTime 占比高的查询 - 替换方案:用
db.collection.createIndex(..., { background: true })在从节点提前建好索引,减少主节点首次查询时的索引构建压力
真正有效的缓存预热方式不是“刷数据”,而是“刷访问模式”
直接遍历全表或随机 find({ _id: ObjectId(...) }) 对性能提升极小——MongoDB 的 WiredTiger 缓存按 4KB 页面管理,且索引页与文档页分离存储。有效预热必须模拟真实查询路径:
- 提取慢查询日志中高频
filter和sort组合,用explain("executionStats")验证是否命中索引 - 对核心集合执行轻量级“探针查询”:如
db.orders.find({ status: "shipped" }).sort({ createdAt: -1 }).limit(1).hint({ status: 1, createdAt: -1 }) - 避免使用
.count()或.estimatedDocumentCount(),它们不触发索引页加载;改用.findOne()+.hint()显式指定索引
WiredTiger cache size 设置不当会让问题雪上加霜
默认 cache size 是物理内存的 50%(wiredTigerCacheSizeGB),但主从切换后若新主节点与其他服务共机,或容器内存限制未同步到 MongoDB 配置,会导致 cache 实际可用远低于预期——索引页频繁被踢出,查询反复缺页。
- 检查
db.serverStatus().wiredTiger.cache中"bytes currently in the cache"与"maximum bytes configured"的比值,持续低于 70% 就说明不够用 - 容器环境必须显式设置
--wiredTigerCacheSizeGB,不能依赖自动计算(cgroup 内存限制不被自动感知) - 索引总大小可估算:
db.collection.stats().totalSize - db.collection.stats().storageSize,确保 cache 至少覆盖热索引+热文档集合
索引加载不可跳过,缓存预热不能靠猜。最常被忽略的是:新主节点的 wiredTigerCacheSizeGB 值可能和旧主不一致,而这个差异在监控图表里几乎不体现,只在 page-tracked-misses 指标里悄悄升高。

















