You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js使用MongoDB原生驱动插入文档如何解决重复键报错

问题根因

当前代码触发重复键报错的核心原因有三个:

  • 异步写法混用:MongoDB原生驱动同时使用await和回调函数,会导致异步时序错乱,重复校验逻辑还没执行完,插入操作就可能被触发,引发竞态。
  • 重复校验逻辑缺失:你仅通过mappingID判断文档是否存在,但MongoDB的_id字段默认自带唯一索引,只要待插入文档的_id和库中已有文档重复,就会触发重复键报错,现有校验完全没覆盖_id字段的重复判断。
  • 「先查后插」逻辑天然存在竞态窗口:高并发场景下,两个相同内容的查询可能同时返回空结果,后续都会执行插入,依然会触发唯一键冲突。
解决方案

根据你单条插入/批量插入数组的不同场景,选对应实现即可:

单条文档插入(修复现有逻辑)

彻底弃用回调写法,统一用Promise风格调用,同时补全重复校验逻辑,增加重复键错误的兜底捕获:

// 待插入文档为result,查询条件覆盖所有带唯一索引的字段,默认必须包含_id
const query = {
  _id: result._id,
  mappingID: id
};
const existingDoc = await consumerContainer.findOne(query);
if (existingDoc) {
  console.log('跳过重复文档,文档ID:', existingDoc._id);
  return;
}
try {
  await consumerContainer.insertOne(result);
} catch (err) {
  // 仅捕获重复键错误,做跳过处理,其余错误正常抛出
  if (err.code === 11000) {
    console.log('触发并发重复插入,跳过文档,重复键信息:', err.keyValue);
  } else {
    throw err;
  }
}

批量插入文档数组(适配数组去重插入需求)

如果是处理一整个待插入的文档数组,不要循环做单条查询+插入,直接用insertMany的无序插入模式,性能更高,还能自动跳过重复文档:

// 第一步:先对待插入数组本身去重,避免数组内部存在重复文档
const seenIds = new Set();
const uniqueDocs = [];
for (const doc of targetDocArray) {
  const docId = doc._id.toHexString();
  if (!seenIds.has(docId)) {
    seenIds.add(docId);
    uniqueDocs.push(doc);
  }
}

try {
  // ordered:false 配置会让MongoDB遇到错误时继续执行后续插入,不会直接中断整个批量操作
  const res = await consumerContainer.insertMany(uniqueDocs, { ordered: false });
  console.log(`批量插入完成,成功插入${res.insertedCount}条文档`);
} catch (err) {
  if (err.code === 11000) {
    const successCount = err.result?.nInserted ?? 0;
    const duplicateCount = err.writeErrors?.length ?? 0;
    console.log(`批量插入完成,成功插入${successCount}条,跳过${duplicateCount}条重复文档`);
  } else {
    throw err;
  }
}

补充说明:如果你判断重复的依据不是_id,而是mappingID这类业务字段,必须先给对应字段创建唯一索引,否则无法彻底规避并发场景下的竞态重复问题,创建索引命令参考:db.你的集合名.createIndex({ mappingID: 1 }, { unique: true })

内容的提问来源于stack exchange,提问作者udit narayan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:24:23