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

如何限制Mongoose中创建关联同一父引用的子文档数量

解决方案

额外疑问解答

MongoDB 原生不支持基于字段重复值数量的索引限制,无法直接通过索引实现同一order值最多存在10条记录的需求,需通过业务逻辑层实现。

方案1:pre('save')中间件(简单实现,适合低并发场景)

直接在planSchema的保存前置钩子中统计对应订单的套餐数量,超出限制则抛出错误:

planSchema.pre('save', async function(next) {
  const planCount = await mongoose.model('Plan').countDocuments({ order: this.order });
  if (planCount >= 10) {
    next(new Error('单个订单最多只能关联10个套餐'));
    return;
  }
  next();
});

注意:该方案在高并发场景下存在竞态问题,若多个创建套餐的请求同时查询计数,可能出现同时通过校验、最终套餐数量超出10个的情况。

方案2:原子计数方案(解决并发竞态,推荐生产使用)

通过在订单表新增套餐计数字段,利用MongoDB单文档写操作的原子性保证计数准确性,完全避免竞态问题:

  1. 首先修改orderSchema,新增planCount字段:
const orderSchema = new mongoose.Schema(
  {
    user: {
      type: mongoose.Schema.ObjectId,
      ref: "User",
      required: [true, "Please provide a user for this order"],
    },
    planCount: {
      type: Number,
      default: 0,
      min: 0,
      max: 10
    },
    //... other fields
  }
);

// 原有虚拟字段保持不变
orderSchema.virtual("plans", {
  foreignField: "order",
  localField: "_id",
  ref: "Plan",
});
  1. 编写套餐创建逻辑,通过事务保证计数和套餐创建的一致性:
async function createPlan(planData) {
  const session = await mongoose.startSession();
  session.startTransaction();
  try {
    // 原子更新订单计数:仅当计数小于10时更新成功
    const updatedOrder = await mongoose.model('Order').findOneAndUpdate(
      { _id: planData.order, planCount: { $lt: 10 } },
      { $inc: { planCount: 1 } },
      { new: true, session }
    );
    if (!updatedOrder) {
      throw new Error('单个订单最多只能关联10个套餐');
    }
    // 创建套餐
    const [newPlan] = await mongoose.model('Plan').create([planData], { session });
    await session.commitTransaction();
    return newPlan;
  } catch (err) {
    await session.abortTransaction();
    throw err;
  } finally {
    session.endSession();
  }
}

如果需要支持套餐删除,在删除逻辑中同步添加对应订单planCount减1的原子更新即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 01:57:03