Mongoose批量更新110万条MongoDB记录耗时过长如何优化
你当前逐次调用findOneAndUpdate的方案,每一次更新都会产生一次独立的网络往返、数据库查询匹配、事务提交流程,110万次操作的绝大多数耗时都浪费在了网络IO和重复的事务开销上,完全没有利用MongoDB的批量处理能力,效率极低。
优先使用分块
bulkWrite无序批量操作
MongoDB原生提供批量写入接口,支持单次请求提交多条更新操作,配合分块逻辑可以把更新效率提升几十到上百倍。注意不要一次性提交全部110万条操作,MongoDB单批操作有16MB的大小限制,单批提交过多也会导致内存占用过高,建议单批大小控制在1000-5000条,根据单条记录的字段长度灵活调整。
参考实现代码:const BATCH_SIZE = 2000; for (let i = 0; i < sourceArray.length; i += BATCH_SIZE) { const currentBatch = sourceArray.slice(i, i + BATCH_SIZE); const operationList = currentBatch.map(record => ({ updateOne: { filter: { a: record.a }, update: { $set: { b: record.b } }, upsert: false // 若需要匹配不到时自动插入新文档可设为true } })); // ordered设为false时MongoDB可以无序并行执行这批操作,速度比有序执行快30%以上 await YourModel.bulkWrite(operationList, { ordered: false }); }提前给匹配字段建立索引
用于匹配文档的字段a必须建立索引,否则无论是单条更新还是批量更新,每次匹配文档都要触发全表扫描,更新速度会被拖得极慢。如果a是文档的唯一标识,建议建立唯一索引,既可以提升匹配速度,也能避免重复数据问题。建索引命令参考:db.yourCollection.createIndex({ a: 1 }) // 唯一索引写法 // db.yourCollection.createIndex({ a: 1 }, { unique: true })离线场景可选写入优化
如果是跑离线全量更新脚本、更新操作支持重跑,可以调整写入关注参数,减少数据库的写入确认开销,进一步提升速度:await YourModel.bulkWrite(operationList, { ordered: false, writeConcern: { w: 1, j: false } });注意:参数
j:false表示写入操作不等待磁盘日志刷盘就返回确认,如果更新过程中数据库进程崩溃,可能丢失最近的少量写入,线上核心业务场景不要用这个配置。其他避坑点
不要在循环里用await逐次等待单条更新返回,这是你当前方案慢的核心原因;批量更新过程中如果脚本跑在公网、和MongoDB实例网络延迟高,建议把脚本部署到和MongoDB同可用区的机器上跑,减少网络传输耗时。
内容的提问来源于stack exchange,提问作者iAviator

