如何限制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单文档写操作的原子性保证计数准确性,完全避免竞态问题:
- 首先修改
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", });
- 编写套餐创建逻辑,通过事务保证计数和套餐创建的一致性:
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
相关产品推荐
相关产品推荐

