使用Mongoose原子更新多父文档内嵌套子文档的库存
嵌套商品变体库存的原子批量扣减方案
核心原理
利用MongoDB原生的原子操作与批量写入能力,完全规避内存计算带来的竞态问题,核心依赖三个特性:
$inc操作符:数据库层面直接执行数值增减,单文档内的操作默认具备原子性arrayFilters条件:精准匹配嵌套数组中的指定变体文档,无需提前查询全量变体bulkWrite批量接口:一次性提交多商品的更新请求,性能远高于循环单条更新
代码实现
步骤1:整理待扣减参数
按照下单商品的实际购买情况,整理出对应商品ID、变体ID、扣减数量的集合:
// 示例参数:可覆盖同商品多变体、多商品多变体的所有场景 const deductList = [ { productId: '60d21b4667d0d8992e610c85', variantId: '60d21b4667d0d8992e610c88', count: 2 }, { productId: '60d21b4667d0d8992e610c85', variantId: '60d21b4667d0d8992e610c89', count: 1 }, { productId: '60d21b4667d0d8992e610c86', variantId: '60d21b4667d0d8992e610c8a', count: 3 } ]
步骤2:构造批量更新操作
给每个更新操作增加库存前置校验,避免超卖:
const bulkOps = deductList.map(item => ({ updateOne: { // 过滤条件:匹配对应商品,且对应变体库存大于等于待扣减数量 filter: { _id: item.productId, variants: { $elemMatch: { _id: item.variantId, quantity: { $gte: item.count } } } }, // 原子扣减指定变体的库存 update: { $inc: { 'variants.$[targetVar].quantity': -item.count } }, // 数组过滤条件:定位要更新的变体 arrayFilters: [ { 'targetVar._id': item.variantId } ] } }))
步骤3:执行操作并校验结果
const result = await Product.bulkWrite(bulkOps) // 匹配成功的条数必须等于待扣减的项数,否则说明存在库存不足的情况 if (result.matchedCount !== deductList.length) { throw new Error('部分商品库存不足,下单失败') } // 校验通过后执行后续订单生成逻辑
额外说明
- 该方案所有库存修改逻辑都在数据库层面原子执行,即便是管理员同时修改库存、多用户同时下单,也不会出现库存计算错误的问题
- 如果需要库存扣减和订单生成的强一致性,可搭配MongoDB事务使用,要求你的MongoDB部署为副本集或分片集群模式
内容的提问来源于stack exchange,提问作者EnragedEarthworm
相关产品推荐
相关产品推荐

