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

Mongoose使用create()插入数据时pre('save')生成重复code如何解决

Mongoose pre('save')生成自增code重复问题排查与解决

问题原因

  • 核心问题是查询最新文档、生成新code、赋值保存的整个流程是非原子操作,存在并发竞争。当你单次调用create()传入多个文档、或者短时间内多次调用create()时,多个pre('save')钩子会并行执行,所有钩子查询集合最新文档时都会拿到同一个旧的code值,最终生成的新code完全相同。
  • 潜在逻辑缺陷:依赖_id倒序获取最新插入文档的规则不可靠。_id前缀虽然是时间戳,但同一毫秒内生成的多个_id排序无法和实际插入顺序完全对应,分片集群场景下该排序规则完全失效。

解决方案

方案1:使用原子计数器(推荐)

单独创建一个计数器集合,用MongoDB的findOneAndUpdate原子操作实现自增序号的获取,彻底避免并发竞争:

  1. 先定义计数器模型:
const CounterSchema = new mongoose.Schema({
  // 标记对应哪个业务集合的计数器,支持多个集合共用该计数器表
  modelName: { type: String, required: true, unique: true },
  // 存储当前最新的数字序号,比直接存字母code更容易做自增操作
  currentSeq: { type: Number, default: 0 }
})
const Counter = mongoose.model('Counter', CounterSchema)
  1. 改写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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 06:09:04