应优先使用 bulkWrite 替代 insertOne 循环和默认 insertMany,因其支持混合操作、并行执行、错误隔离;需合理设 batch_size(目标单批300–800ms)、关闭非必要索引、预分片、检查 writeErrors。

直接用 bulkWrite,别碰 insertOne 循环,也别默认用 insertMany——百万级写入慢,90% 是因为没拆批、没关索引、没设 ordered: false。
为什么 insertMany 不是万能解药
insertMany 确实比 insertOne 快很多,但它仍是“单操作类型+有序执行”的封装,不支持混合写(如插+更+删),错误处理僵硬,默认遇到第一个重复键就中断整批;更重要的是,它无法绕过 MongoDB 服务端对单次请求的隐式限制(比如 maxWriteBatchSize),在分片集群中仍走串行路由。真实压测中,bulkWrite 吞吐可比 insertMany 高出 2–3 倍。
实操建议:
- 明确替换所有
insertMany([...])调用为bulkWrite([{ insertOne: { document: ... } }, ...]) - 若只做纯插入,
insertMany可用,但必须配合ordered: false参数(pymongo 传ordered=False,Node.js 驱动传{ ordered: false }) - 避免在已有大量数据的集合上直接跑
insertMany—— 它不会跳过已存在索引键,容易触发大量duplicate key错误并中断
batch_size 设成 1000 还是 5000?看单批耗时,不是看条数
批次大小不是拍脑袋定的。设太小(如 100),网络压缩收益低;设太大(如 10000+),可能触发 BSON 16MB 限制、WiredTiger 内存压力或连接超时。关键指标是「单批执行耗时」:目标控制在 300–800ms 区间最稳。
实操建议:
- 起始用
batch_size = 2000,用mongostat观察netIn和opcounters.bulk是否平稳上升 - 若出现
SocketTimeoutError或客户端报Network timeout,立刻降到1000 - 若
bulkWrite返回里writeConcernError频发,说明副本集从节点 lag 高,该查rs.printSecondaryReplicationInfo(),而不是调大 batch - 别用
Math.ceil(total / 2000)硬算批次——每批数据体积差异大(比如含长文本字段),应按实际序列化后字节估算
ordered: false 不是加速开关,是错误兜底开关
设 ordered: false 后,MongoDB 并行执行所有操作,失败项(如重复键、类型校验失败)不影响其余;但它不会抛异常,也不会中断流程——错误被静默塞进返回值的 writeErrors 字段里。日志、埋点、ETL 场景下这是刚需,但漏查 writeErrors 就等于丢数据。
实操建议:
- 必须显式检查返回结果:
if (result.writeErrors.length > 0) { /* 记录或重试 */ } - 在分片集群中,
ordered: false让mongos并发投递,否则默认串行,吞吐直接砍半 - Node.js 驱动 v4.0+、pymongo v3.12+ 才完整支持
writeErrors结构;旧版本返回格式不一致,容易误判 - 不要以为设了
ordered: false就可以忽略唯一索引——它只是让失败不中断,但冲突仍会记录为 error,不解决根本问题
真正卡住百万插入的,往往是索引和分片键
即使 bulkWrite 跑得飞快,如果集合已有多个二级索引,每插一条都要更新所有索引树,I/O 毛刺和 page split 会指数级上升。更隐蔽的是分片键设计:若用时间戳或自增 ID 作分片键,所有新数据都打到同一个分片,等于单点写入,集群白搭。
实操建议:
- 插入前用
db.collection.getIndexes()查索引,非必要索引全删:db.collection.dropIndex("xxx_1") - 插入完成后再重建索引:
db.collection.createIndex({ ... }),此时 WiredTiger 会做有序批量构建,比边插边建快 5–10 倍 - 空集合首次批量导入,务必预分片:
sh.shardCollection("db.coll", { shardKey: 1 }),避免初始块只落在一个分片 - 若分片键无法改(如业务强依赖时间范围查询),至少把 ObjectId 反转或打乱高低位,打破单调性
最常被跳过的一步:没确认驱动版本是否支持完整错误结构,也没检查 writeErrors —— 看似跑完了,其实 30% 的文档因键冲突静默失败了。

















