Mongodb内嵌文档的upsert需求实现

最近在工作有需要使用 mongodb 的 upsert 实现,起初表设计是这样的:

// 表 youlun
{
pid : 1234, //{产品Id},
name: “超级邮轮”, //{产品名字},
priceDate: [ //这个字段是价格日历,里面存储了每天这个产品的价格和库存
{
date: 20161219, //售卖日期
price: 1000, //当天的售卖价格
rate: 80, //售卖折扣
stock: 100, //库存
},
… …
]
}, …
这里忽略了其他字段,这里的库存这么存仅仅是为了说明问题,读者不要深究哦

1、当我们需要更新某一个售卖日期的价格的时候,语句可以这么写:
db.getCollection(‘youlun’).update(
{pid:1234, “priceDate.date”:20161219},
{$set:{“priceDate.$.price”:1200}}
)
2、当我们需要更新整个某天的售卖政策时:
db.getCollection(‘youlun’).update(
{pid:1234, “priceDate.date”:20161219},
{$set:{
“priceDate.$”:{date:20161219, price:1300, rate:70, stock:70}
}}
)
3、当我们需要新增加一条日期价格时
db.getCollection(‘youlun’).update(
{pid:1234},
{$push:{
priceDate:{date:20161220, price:1200, rate:80, stock:90}
}
})
4、当我们不确定是否需要插入一条新的pid产品政策时,需要这样写
db.getCollection(‘youlun’).update(
{pid:1235},
{$push:{
priceDate:{date:20161219, price:1200, rate:80, stock:90}
}
}, {upsert:true})
5、当我们想要删除某一个日期的全部价格时
db.getCollection(‘youlun’).update(
{pid:1235},
{ $pull:{
priceDate:{date:20161219}
}
})

似乎一切看上去很美妙,但是问题来了,我们这个数据库的调用方,完全不知道这个pid是否存在,也不知道价格日期政策是否存在,程序无法确定是调用$push还是$set。
如果我们先从数据库将数据全部查出来再更新,
如果有2个并发的对 priceDate 字段的更新就将是灾难性的,无法保证原子性的操作!

现在,再整理一下我们的需求,我们的数据库服务必须要满足如下几个点:
1、对于不存在pid,需要自动创建这条pid记录
2、对于已经存在的pid,用户传递一个或多个价格政策全量,如下就是全量:
{date:20161219, price:1200, rate:80, stock:90}
对于上面的这种全量,要动态的感知是更新掉原来的价格政策,
还是新增加一个价格政策,同时要保证一天只能有一个这样的内嵌文档政策

3、还需要支持只修改 date:20161219 的价格,而其他内容不变,
比如:仅仅将 date日期为20161219这个政策的售卖价格修改为 900 元,
其他库存,折扣率不变

说了这么多,头都大了,那我们究竟改怎么调整才能满足上面3个需求呢?
很显然,数组类型的内嵌文档销售策略数据存储的方式已经然并卵了。

经过调整,我们最终的表结构如下:

// 表 youlun
{
pid : 1234, //{产品Id},
name: “超级邮轮”, //{产品名字},
priceDate: //这个字段是价格日历,里面存储了每天这个产品的价格和库存
{
“20161219”: { //售卖日期,售卖日期
price: 1000, //当天的售卖价格
rate: 80, //售卖折扣
stock: 100, //库存
},
… …
}
}, …
我们同样写一下几个需求的mongodb语句
1、当我们需要更新某一个售卖日期的价格的时候,语句可以这么写:
db.getCollection(‘youlun’).update(
{pid:1234},
{
$set:{“priceDate.20161219.price”:1200}
},
{ upsert:true }
)

2、当我们需要更新整个某天的售卖政策时:
db.getCollection(‘youlun’).update(
{pid:1234},
{
$set:{ “priceDate.20161219”:{price:1300, rate:70, stock:70} }
},
{ upsert:true }
)

3、当我们需要新增加一条日期价格时
db.getCollection(‘youlun’).update(
{pid:1234},
{
$set:{ “priceDate.20161220”:{price:1200, rate:80, stock:90} }
},
{ upsert:true }
)

4、当我们不确定是否需要插入一条新的pid产品政策时,需要这样写
db.getCollection(‘youlun’).update(
{pid:1235},
{
$set:{ “priceDate.20161219”:{price:1200, rate:80, stock:90} }
},
{ upsert:true }
)

5、当我们想要删除某一个日期的价格时
db.getCollection(‘youlun’).update(
{pid:1235},
{
$unset:{ “priceDate.20161219”:1}
}
)

使用Map的结构,可以避免同一天,有多个价格政策的问题,
同时上述的每一个语句都是原子操作,就算并发操作也没有问题

我们可以根据程序判断需要更新的某一天价格政策的字段是否为全字段,
如果非全字段就单独更新,
否则全量更新,大家发现全量更新的语句一模一样,至此我们的问题解决了,
明天改代码,然后献上压力测试结果,不知道还是否有坑~