在Mongoose中如何通过多个对象ID对对象数组执行upsert操作
问题根因
- 你使用了两次独立的
findOneAndUpdate操作,即便加了await,极端情况下仍可能出现两次操作间隙有其他写入修改了目标文档,导致pull还没生效就执行了push findOneAndUpdate的multi参数无效,该参数仅对updateMany生效,不过你是按唯一id匹配文档,这个点不影响你的场景- 拆分两次操作无法保证动作的原子性,一旦第一次pull执行失败(比如参数传错、匹配不到文档),第二次push仍会执行,直接导致数组重复
修复方案
方案1:合并为单次原子更新(推荐)
直接把$pull和$push写在同一个更新语句里,MongoDB会保证两个操作原子执行,不会出现中间态:
await ResponseModel.findOneAndUpdate( { id: updatedResponse.id }, { $pull: { response_elements: { id: { $in: elementIdsToUpdate } } }, $push: { response_elements: { $each: response_elements } } }, { upsert: false } )
方案2:用$addToSet替代(适用元素完全相等场景)
如果你的response_elements整个对象更新前后没有其他差异,只是需要去重,可以直接用$addToSet配合$each,不需要提前pull:
await ResponseModel.findOneAndUpdate( { id: updatedResponse.id }, { $addToSet: { response_elements: { $each: response_elements } } }, { upsert: false } )
注意:
$addToSet是按整个对象的内容做全等匹配,如果更新前后元素的id相同但其他字段有变化,这个方案不适用,必须用方案1。
额外校验点
- 确认
elementIdsToUpdate数组的元素类型和response_elements中id字段的类型完全一致,比如如果id是ObjectId类型,不要传字符串格式的id,会导致$in匹配不到,pull失效 - 确认
response_elements数组中要更新的元素id确实都包含在elementIdsToUpdate里,漏传的id不会被pull,就会出现新旧元素共存的情况
内容的提问来源于stack exchange,提问作者Danyolo
相关产品推荐
相关产品推荐

