insertMany本身不阻塞事件循环,但批量过大、ordered:true、writeConcern过严、连接池不足或后续读风暴等错误配置会使其变成“伪同步”操作,导致事件循环严重延迟。

insertMany本身不阻塞事件循环,但错误配置会让它变成“伪同步”操作
Node.js 的 insertMany 是异步函数,底层走的是非阻塞 I/O。但它会卡住事件循环,根本原因不是函数本身,而是你没控制好「批量大小」和「等待行为」。比如一次塞 5 万条文档进去,驱动内部会把这批数据序列化、分片、发包——这个过程在主线程上做大量 JSON 序列化和 Buffer 拼接,CPU 时间片被占满,后续的 setTimeout、Promise.then 全部排队等它做完。
为什么 ordered: false + writeConcern: { w: 1, j: false } 能缓解阻塞
默认 ordered: true 意味着驱动必须严格按序处理每条文档,遇到失败就中断,整个流程更重;而 ordered: false 允许并行校验和写入路径优化。更重要的是 writeConcern: { w: 1, j: false }:它让驱动只等主节点内存写入完成,不等磁盘刷 journal,省掉几百毫秒的同步等待——这部分等待虽是异步的,但会拖长整个 Promise 生命周期,间接拉高事件循环负载。
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
-
w: "majority"(默认)会卡在副本集同步确认上,可能等 1–2 秒 -
j: true强制等 journal 刷盘,对 SSD 都有明显延迟,HDD 更甚 - 不设
timeoutMS时,网络抖动会导致 Promise 悬停数分钟,事件循环持续空转轮询
真正压垮事件循环的往往是连接池 + 慢查询组合
你以为你在跑 insertMany,其实背后可能是连接池已满,新请求全卡在 waitQueue 里。一旦某个 find 查询因缺失索引跑出 COLLSCAN,占用连接超过 10 秒,poolSize=10 的默认配置下,第 11 个 insertMany 就开始排队,每个都 await 不返回,事件循环里堆满 pending Promise——此时 CPU 不高,但响应全挂起。
- 用
db.currentOp({ secs_running: { $gt: 5 } })查看是否真有长任务占着连接 -
maxPoolSize建议设为 20–50,但前提是后端 MongoDB 能扛住并发连接 - 别在插入前开 profiling 或日志采样,那些同步 IO 会直接钉死主线程
大数组切片不光为防 OOM,更是保护事件循环的节奏感
把 10 万条数据 .slice(0, 500) 分批,不只是避免内存爆炸。每次 await collection.insertMany(chunk) 后加 await new Promise(r => setTimeout(r, 0)),能强制让出当前事件循环 tick,给其他微任务(如日志 flush、健康检查)执行机会。无脑 Promise.allSettled(chunks.map(...)) 反而更容易触发连接池争抢和 TCP 包拥塞。
- 500–1000 条/批是实测平衡点:再小吞吐低,再大序列化压力陡增
- 避免在循环里拼大字符串或构造深嵌套对象,JSON.stringify() 会同步吃掉 V8 堆栈
- 如果文档含
Buffer或二进制字段,提前用toJSON()剥离,否则序列化耗时翻倍
insertMany 返回的 result.insertedIds 是同步生成的 ObjectId 数组,但如果你在回调里又立刻发起 1000 次 findOne 去查刚插的数据,那事件循环就不是被写入阻塞,而是被你自己发起的读风暴拖垮。

















