MongoDB bulkWrite插入随机生成_id仍报E11000重复键错误
问题产生原因
报错核心是代码逻辑存在三个明显缺陷,和集合初始为空、新生成随机ID重复无关:
- bulkWrite调用逻辑完全错误:循环内只要
destinations数组不为空就触发批量写入,但写入完成后从未清空数组。第一次循环数组存入1条文档并执行写入,第二次循环会往数组里追加第二条文档,此时数组共2条记录(包含第一次已经写入过的文档),再次触发bulkWrite就会重复提交已存在的文档,直接触发_id唯一键冲突。 - 异步操作未加等待:
bulkWrite是异步方法,原代码没有加await关键字,调用后不会等待写入完成就进入下一轮循环,会导致写入时序和数组状态混乱,进一步放大重复提交问题。 - 自定义随机ID生成方案不可靠:
Math.random()是伪随机数,乘以10000000000000取整的生成方式本身就存在碰撞概率,不是合法的唯一ID生成方案。
修复方案
按以下三点调整代码即可解决问题:
- 调整批量写入逻辑:不要每轮循环都触发写入,选择循环结束后统一写入,或者按固定批次(如每500条)写入,每次写入完成后必须清空待操作数组。
- 所有异步数据库操作前加
await,保证时序正确。 - 废弃自定义随机ID生成逻辑,改用MongoDB官方提供的
ObjectId生成唯一主键,从根源避免ID碰撞。
修复后的完整代码如下:
import listingDestinations from '/common/info/listingDestinations'; import { ObjectId } from 'mongodb'; // Meteor环境可替换为从meteor/mongo引入ObjectId Migrations.add({ version: 329, name: 'Add ListingDestinations', async up() { const {default: ListingDestination} = require('/common/db/listingDestination'); let destinations = []; for (const listingDestination of listingDestinations) { const _id = new ObjectId(); destinations.push({ insertOne: { document: { _id, ...listingDestination, }, }, }); // 分批写入,避免单次批量操作数据量过大 if (destinations.length >= 500) { await ListingDestination.rawCollection().bulkWrite(destinations); destinations = []; } } // 写入最后一批不足500条的剩余数据 if (destinations.length > 0) { await ListingDestination.rawCollection().bulkWrite(destinations); } }, });
内容的提问来源于stack exchange,提问作者Avinash Toppo
相关产品推荐
相关产品推荐

