如何精简MongoDB查询实现数组元素存在则更新count不存在则插入
MongoDB单语句实现数组元素更新/插入原子操作
需求说明
- 匹配
from、to字段的目标文档 - 若文档内
json数组已存在对应asset_id的元素,将该元素的count值加1 - 若数组内不存在对应
asset_id的元素,向数组插入格式为{asset_id: 目标id, count: 1}的新元素 - 支持主文档不存在时自动创建(upsert)
目标文档结构如下:
{ from: '1', to: '2', json:[ {asset_id: id, count: 1}, {asset_id: id, count: 1}, {asset_id: id, count: 1} ] }
原有方案问题
当前拆分3条updateOne执行的写法存在冗余,且并发场景下会出现竞态问题,比如两条请求同时判断到元素不存在,会重复插入元素。使用MongoDB 4.2+版本支持的聚合管道更新能力,可以单条原子语句实现全部逻辑。
单语句实现代码
db.你的集合名.updateOne( // 主文档匹配条件 { from: res[i].from, to: res[i].to }, [ // 第一阶段:初始化字段,兼容upsert新建文档的场景 { $set: { from: res[i].from, to: res[i].to, json: { $ifNull: ["$json", []] } } }, // 第二阶段:判断数组元素是否存在,分别处理计数+1/追加新元素逻辑 { $set: { json: { $cond: [ // 判断条件:数组内是否已存在对应asset_id { $anyElementTrue: { $map: { input: "$json", as: "item", in: { $eq: ["$$item.asset_id", res[i].asset_id] } } } }, // 存在则遍历数组,匹配项count+1 { $map: { input: "$json", as: "item", in: { $cond: [ { $eq: ["$$item.asset_id", res[i].asset_id] }, { $mergeObjects: ["$$item", { count: { $add: ["$$item.count", 1] } }] }, "$$item" ] } } }, // 不存在则拼接新元素到数组末尾 { $concatArrays: ["$json", [{ asset_id: res[i].asset_id, count: 1 }]] } ] } } } ], // 开启upsert,主文档不存在时自动创建 { upsert: true } )
注意事项
- 该写法依赖MongoDB 4.2及以上版本的聚合管道更新能力,目前主流线上MongoDB实例均支持该版本
- 整个更新过程是原子执行的,不会出现拆分多条语句时的并发写入冲突问题,无需额外加事务
- 如果使用的MongoDB版本低于4.2,没有原生单语句实现该需求的方案,必须分两次查询判断后再更新,建议升级实例版本
内容的提问来源于stack exchange,提问作者user25688
相关产品推荐
相关产品推荐

