MongoDB并行插入100条记录时的性能差异问题
MongoDB并行插入性能下降的原因及解决办法
性能差异的原因
- 连接池耗尽:MongoDB Node.js驱动默认连接池大小为100,当一次性发起100个
insertOne请求时,连接池被完全占满。服务器端需要同时处理所有连接的请求,每个请求的等待和处理时间被直接拉长。 - 服务器资源竞争:Atlas M30属于共享型集群,CPU、磁盘IO和网络带宽都有上限。100个并发插入请求会引发服务器端上下文切换、文档锁协调开销(虽为文档级锁,但批量并发仍有资源竞争),以及网络带宽饱和,直接导致单请求响应时间飙升。
- 客户端并发压力:一次性触发100个异步Promise,客户端事件循环需要处理大量网络回调,加上网络往返的排队延迟,进一步增加了单条插入的耗时。
解决办法
1. 控制并发请求数
避免一次性发起全部请求,采用分批并发的方式,限制同时进行的插入请求数量,让服务器和连接池有足够资源处理请求。可以使用p-limit库简化控制:
import { MongoClient } from 'mongodb'; import pLimit from 'p-limit'; const mc = new MongoClient("mongodb://localhost:27017/"); // 限制并发数为20,可根据实际情况调整 const limit = pLimit(20); const getConnection = async function () { const client = await mc.connect(); const db = client.db('testpeformance'); return db.collection("candidate"); }; const insertToMongo = async function (collection, index) { const startTime = new Date(); const result = await collection.insertOne({ "FirstName": "TESTPERF", "LastName": "TESTPERF", "Designation": "TESTPERF", "EmailId": "dhanilan@undaku.com" }); const endTime = new Date(); const insertedId = result.insertedId; console.log(`Inserting record [${index}] - ${insertedId} took ${endTime.getTime() - startTime.getTime()}ms`); }; const run = async function () { const connection = await getConnection(); console.time('overall'); const promises = []; for (let index = 0; index < 100; index++) { promises.push(limit(() => insertToMongo(connection, index))); } await Promise.all(promises); console.timeEnd('overall'); }; run();
如果不想额外引入库,也可以手动实现分批处理:
const run = async function () { const connection = await getConnection(); console.time('overall'); const batchSize = 20; // 每批并发数 for (let topIndex = 0; topIndex < 100; topIndex += batchSize) { const promises = []; for (let index = topIndex; index < Math.min(topIndex + batchSize, 100); index++) { promises.push(insertToMongo(connection, index)); } await Promise.all(promises); } console.timeEnd('overall'); };
2. 调整连接池大小
根据服务器承载能力,适当调整MongoDB客户端的连接池大小,避免连接池成为瓶颈。初始化MongoClient时配置maxPoolSize:
const mc = new MongoClient("mongodb://localhost:27017/", { maxPoolSize: 150 // 不要超过Atlas M30的连接数上限(约1000/节点) });
3. 优化预处理与插入的并行逻辑
虽然无法使用批量插入,但可以将预处理和插入操作重叠执行:比如预处理第1-20条文档的同时,发起前一批(0-19)的插入请求,减少整体等待时间。
4. 临时调整索引
如果集合上存在非必要索引,大量插入时会产生索引更新的额外开销。可以在插入前临时删除非必需索引,插入完成后再重建:
// 插入前删除索引 await connection.dropIndex("EmailId_1"); // 执行插入操作... // 插入完成后重建索引 await connection.createIndex({ EmailId: 1 });
5. 检查Atlas实例负载
登录Atlas控制台查看M30实例的CPU、磁盘IO、网络使用率,如果已接近上限,可考虑升级实例规格(如M40),或调整实例配置(如增加节点)。
内容的提问来源于stack exchange,提问作者Dhanilan
相关产品推荐
相关产品推荐

