MongoDB数据校验存储问题:重复数据未触发预期报错
问题排查:重复数据校验未触发报错
问题描述
尝试实现数据存储逻辑:先校验数据是否存在,若已存在则抛出错误,否则存储数据。现有代码包含重复的name与icc字段数据,但保存第一条记录后未触发预期的重复报错。
代码示例
let Records = [{ name: 'test', icc: 'testId' }, { name: 'test', icc: 'testId' }] await Promise.all(Records.map(async (item) => { try { item['id'] = id let ItemObj = table_name.build(); item.name = item.name; item.icc = item.icc; let found = await table_name.findOne({ where: { name: item.name, id: id } }) console.log("11111111111111111"); if (found) { console.log("22222222222222222222"); throw new Error(item.name + " is already exist!"); } else { console.log("333333333333333333333333333333"); Object.assign(ItemObj.dataValues, item); console.log("4444444444444444444") await ItemObj.save() console.log("5555555555555555555") } } catch (err) { throw new Error(err); } }) ).then((data) => { console.log("final"); }).catch((err) => { console.log("error logged"); })
控制台输出
11111111111111111 33333333333333333 44444444444444444 11111111111111111 33333333333333333 44444444444444444 55555555555555555 55555555555555555
问题原因及解决方案
核心问题:并发竞态条件
使用Promise.all同时处理两条记录时,两个校验查询几乎同时执行,此时第一条记录还未完成保存操作,第二条记录的查询自然找不到已存在的数据,最终两条记录都成功保存,无法触发重复校验逻辑。
解决方案
- 串行处理记录
将并行处理改为串行遍历,确保前一条记录完成校验和保存后,再处理下一条,避免竞态条件:
for (const item of Records) { try { item['id'] = id; let ItemObj = table_name.build(); let found = await table_name.findOne({ where: { name: item.name, id: id } }); console.log("11111111111111111"); if (found) { console.log("22222222222222222222"); throw new Error(item.name + " 已存在!"); } else { console.log("333333333333333333333333333333"); Object.assign(ItemObj.dataValues, item); console.log("4444444444444444444"); await ItemObj.save(); console.log("5555555555555555555"); } } catch (err) { throw new Error(err); } }
- 数据库层面添加唯一约束
这是最可靠的方案,直接在数据库中给name和id(或你需要的组合字段)添加唯一索引,即使代码层面出现并发问题,数据库也会直接抛出重复键错误,从根源避免重复数据:
- 若使用MongoDB,执行以下命令创建复合唯一索引:
db.collection.createIndex({ name: 1, id: 1 }, { unique: true }) - 若使用Sequelize(从代码的
build、findOne方法判断更接近关系型数据库操作),可在模型定义中添加复合唯一约束:name: { type: DataTypes.STRING, allowNull: false, unique: 'compositeIndex' }, id: { type: DataTypes.STRING, allowNull: false, unique: 'compositeIndex' }
- 清理冗余代码
代码中item.name = item.name;和item.icc = item.icc;属于无效冗余代码,可直接删除。
内容的提问来源于stack exchange,提问作者wiilan
相关产品推荐
相关产品推荐

