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
相关产品推荐
相关产品推荐

