Mongoose使用create()插入数据时pre('save')生成重复code如何解决
Mongoose pre('save')生成自增code重复问题排查与解决
问题原因
- 核心问题是查询最新文档、生成新code、赋值保存的整个流程是非原子操作,存在并发竞争。当你单次调用
create()传入多个文档、或者短时间内多次调用create()时,多个pre('save')钩子会并行执行,所有钩子查询集合最新文档时都会拿到同一个旧的code值,最终生成的新code完全相同。 - 潜在逻辑缺陷:依赖
_id倒序获取最新插入文档的规则不可靠。_id前缀虽然是时间戳,但同一毫秒内生成的多个_id排序无法和实际插入顺序完全对应,分片集群场景下该排序规则完全失效。
解决方案
方案1:使用原子计数器(推荐)
单独创建一个计数器集合,用MongoDB的findOneAndUpdate原子操作实现自增序号的获取,彻底避免并发竞争:
- 先定义计数器模型:
const CounterSchema = new mongoose.Schema({ // 标记对应哪个业务集合的计数器,支持多个集合共用该计数器表 modelName: { type: String, required: true, unique: true }, // 存储当前最新的数字序号,比直接存字母code更容易做自增操作 currentSeq: { type: Number, default: 0 } }) const Counter = mongoose.model('Counter', CounterSchema)
- 改写
pre('save')钩子:
baseAttribute.pre('save', async function(next) { try { // 原子性更新计数器:不存在则初始化,存在则序号+1,返回更新后的最新值 const counter = await Counter.findOneAndUpdate( { modelName: 'BaseAttributes' }, { $inc: { currentSeq: 1 } }, { new: true, upsert: true } ) // 将数字序号转为你需要的字母code格式,可复用原有GetAlphaCode逻辑做适配 this.code = Codes.GetAlphaCodeBySeq(counter.currentSeq) next() } catch (err) { next(err) } })
方案2:批量插入场景适配
如果你需要批量插入大量文档,不建议直接调用Model.create([doc1, doc2, ...docN]),该方式会触发N个并行的save钩子,依然存在竞争:
- 要么改为串行插入文档,确保前一个文档插入完成后再插入下一个
- 要么提前批量生成所有不重复的code,赋值给文档后再一次性调用
create插入
额外优化建议
你原有代码中异步pre('save')钩子的next()调用方式没有直接导致当前问题,但建议给异步逻辑包裹try/catch捕获异常,出现错误时调用next(err)传递错误,避免请求无响应。
内容的提问来源于stack exchange,提问作者AtaKon
相关产品推荐
相关产品推荐

