新增字段需先评估写放大和锁行为:千万级集合执行updateMany会全表扫描并逐文档写入,导致数小时延迟;建索引必须用background:true并避开主节点刚切换后的5分钟窗口;null/缺失值默认入索引,应使用partialFilterExpression限定范围;后台索引进度需结合currentOp、mongostat及collection.stats()综合判断。

新增字段前先评估写放大和锁行为
直接对千万级集合执行 db.collection.updateMany({}) 补全字段,会触发全表扫描+逐文档写入,在 WiredTiger 引擎下可能持续数小时,且期间写入延迟明显升高。MongoDB 4.2+ 虽支持后台索引构建,但字段补全本身不自动后台化。
- 优先用
db.collection.updateMany({}, {$set: {new_field: null}}, {multi: true, bypassDocumentValidation: true}),加bypassDocumentValidation避免校验开销 - 若集合启用了
documentValidation,必须显式绕过,否则可能因 schema 不匹配中断 - 生产环境务必在低峰期执行,观察
db.currentOp({secs_running: {$gt: 60}})确认无长事务阻塞
建索引必须用 background: true 且避开主节点选举窗口
默认同步建索引会阻塞读写,尤其在副本集里,若主节点正在重建索引,又遇上心跳超时或网络抖动,极易触发非预期的主从切换——新主节点可能还没完成索引构建,导致后续查询报 IndexNotFound 错误。
- 建索引命令必须带
{background: true},例如:db.collection.createIndex({new_field: 1}, {background: true}) - 不要在
rs.status().members[n].stateStr === "PRIMARY"刚变更后的 5 分钟内建索引,此时 oplog 同步可能未稳定 -
background: true不等于“完全不占资源”,它仍会竞争 I/O 和内存,建议配合maxTimeMS控制单次操作时长
字段值为空时索引行为与查询性能陷阱
如果补字段时统一设为 null 或缺失,MongoDB 默认会把 null 和缺失字段都存进索引(除非显式排除),这会导致索引体积虚高、范围查询效率下降,甚至让 {new_field: {$exists: true}} 这类查询无法走索引。
- 建索引时如只需覆盖非空值,用
{partialFilterExpression: {new_field: {$type: "string"}}}限定范围 - 若字段后期大量为
null,考虑改用{$set: {new_field: {$cond: [{$eq: ["$old_field", null]}, "$$REMOVE", "$old_field"]}}}动态跳过写入 - 验证索引是否生效:用
db.collection.find({new_field: "val"}).explain("executionStats")查看nReturned和totalDocsExamined是否接近
监控索引构建进度不能只看 currentOp
db.currentOp() 只显示“正在运行”的操作,但后台索引构建实际由多个短生命周期任务组成,容易漏看;更关键的是,它不反映磁盘写入进度或内存占用趋势。
- 查真实进度用:
db.adminCommand({currentOp: {filters: {secs_running: {$gt: 10}, "command.createIndexes": {$exists: true}}}}) - 结合系统层监控:
mongostat --host rs0/localhost:27017观察netIn/netOut和idx列变化 - 索引文件大小增长滞后于逻辑进度,
du -sh ./data/db/mydb.mycol.*在 WiredTiger 下不可靠,应以db.collection.stats().indexCount和indexDetails为准
最麻烦的不是慢,而是索引中途失败后残留的 incomplete 状态——它不会报错,但后续同名索引创建会静默跳过,得手动 db.collection.dropIndex("new_field_1") 清理再试。

















