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

如何将Mongoose字段默认值设为数据库现有条目数量?

解决Mongoose Schema中动态生成连续唯一排序索引的问题

我之前正好处理过类似的需求——要给新创建的文档生成一个默认的连续唯一排序值,同时允许用户手动修改。直接在Schema的default选项里写是行不通的,因为default只能是静态值或者同步函数,没法动态查询数据库里的已有条目数量。下面给你两种可行的实现方案,兼顾基本场景和并发安全:

方案一:基础预保存中间件(适合低并发场景)

利用Mongoose的pre('save')预保存钩子,在文档保存前动态查询当前集合的文档数量,以此作为sorting的默认值:

const CategorySchema = Schema({
  title: String,
  sorting: {
    type: Number,
    index: { unique: true },
    // 这里不设置默认值,交给中间件处理
  },
  creationDate: { type: Date, default: Date.now },
});

// 预保存中间件:仅对新创建且未手动设置sorting的文档生效
CategorySchema.pre('save', async function(next) {
  if (this.isNew && typeof this.sorting === 'undefined') {
    try {
      // 获取当前Category集合的文档总数
      const docCount = await this.constructor.countDocuments();
      // 第一个文档count为0,正好对应sorting=0,后续依次递增
      this.sorting = docCount;
      next();
    } catch (error) {
      next(error);
    }
  } else {
    // 若已手动设置sorting或不是新文档,直接进入下一步
    next();
  }
});

const Category = mongoose.model('Category', CategorySchema);

注意事项

这个方案在低并发场景下完全够用,但如果有多个请求同时创建文档,可能会出现多个请求拿到相同的docCount,导致sorting重复触发唯一索引冲突。如果你的业务有高并发创建的需求,建议用下面的方案。

方案二:原子计数器(高并发安全)

通过创建一个独立的计数器集合,利用MongoDB的findOneAndUpdate原子操作来递增计数,彻底避免并发冲突:

步骤1:定义计数器Schema

// 创建专门的计数器模型,记录每个集合的当前最大排序值
const CounterSchema = new Schema({
  modelName: { type: String, required: true, unique: true },
  currentCount: { type: Number, default: 0 }
});
const Counter = mongoose.model('Counter', CounterSchema);

步骤2:修改Category的预保存中间件

const CategorySchema = Schema({
  title: String,
  sorting: {
    type: Number,
    index: { unique: true },
  },
  creationDate: { type: Date, default: Date.now },
});

CategorySchema.pre('save', async function(next) {
  if (this.isNew && typeof this.sorting === 'undefined') {
    try {
      // 原子操作:找到对应模型的计数器,不存在则创建,同时递增计数
      const updatedCounter = await Counter.findOneAndUpdate(
        { modelName: 'Category' },
        { $inc: { currentCount: 1 } },
        { upsert: true, new: true } // upsert表示不存在则插入,new返回更新后的文档
      );
      // 因为第一次递增后currentCount是1,所以sorting要减1,保证第一个文档是0
      this.sorting = updatedCounter.currentCount - 1;
      next();
    } catch (error) {
      next(error);
    }
  } else {
    next();
  }
});

const Category = mongoose.model('Category', CategorySchema);

优势

  • findOneAndUpdate是MongoDB的原子操作,多个请求同时触发时会排队执行,不会出现计数重复的问题
  • 计数器独立存储,后续如果需要重置排序值或者批量调整,操作更灵活

额外提示

  • 如果用户手动设置了sorting值,中间件会自动跳过默认值生成逻辑,满足自定义排序的需求
  • 要记得在业务代码中捕获DuplicateKeyError(当手动设置的sorting值已存在时抛出),给用户友好的错误提示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:59:22