业务中经常有这样的场景:当新增某需求时,业务需要使用新表,该表的记录之前的数据没有,业务上线后需要保证对每个业务实体,该表的记录只有一条
举个例子:当官网上线新手引导任务功能时,需要在新建的任务表,为每个站点记录该站点完成了哪些任务,跳过了哪些任务,对于之前创建的站点,任务表显然是没有该记录
此时一般有两种做法:
但是第二种做法中,如果没有给任务表针对站点id加唯一索引,可能因为并发问题插入两条记录。如果加了唯一索引(建议加上),在并发操作中有些请求在第一次插入时遇到唯一键冲突,需要手动改为更新操作进行重试,比较麻烦
有没有比较优雅的写法呢?
mongodb提供了upsert
操作,实现没有就插入,否则更新的功能
我们看一个例子,表site_task用于保存每个站点完成的任务,有两个字段:
执行如下操作:
db.site_task.updateOne(
{
site_id:"a"
}, // filter部分
{
$addToSet:
{
task_info:{
task_id:"123", // 任务id
status:1, // 1:已完成
operator:112 // 操作人
}
}
}, // update部分
{
upsert:true
}
)
往任务表中插入站点a的任务记录,如果站点a不存在,就插入该记录,内容为filter和update部分,否则就更新。
需要注意的是,使用mongodb的upsert
时,需要给filter用到的字段设置唯一索引
因为update
操作不是原子的,分为两步:先搜索文档,再更新
upsert
操作时,当两个请求同时发现该文档不存在,都进行插入,在没有唯一索引约束的情况下就会插入重复记录检测和插入操作也不可能在一个原子操作中,因为此时该记录不存在,不可能在该记录加锁,若要保证原子性,就需要加类似表锁的锁来防止并发插入,成本较大
解决方法就是在filter用到的条件加唯一索引,当insert
出现duplicate key异常
时,重试该操作即可,此时重试就一定执行update
,符合预期。在4.1.6版本以后会自动重试(SERVER-37124),若使用4.1.6版本以下,需要看看使用的sdk是否针对这种情况自动重试,否则需要手动重试
除了文档本身,若文档有数组类型的字段,也可能出现并发问题
当记录有了后,需要往数组类型的字段中追加数据时,若采用先查出数据,在服务中追加,再写入数据库的方式,遇到并发时可能会更新丢失
mongdb给数组追加提供了两个修改器:$push
和$addToSet
$push:直接往数组后面追加
$addToSet:若数组中有该元素,就忽略,否则追加
例如:当执行如下操作后,
db.site_task.updateOne(
{
site_id:"a"
}, // filter部分
{
$addToSet:
{
task_info:{
task_id:"123",
status:1,
operator:112
}
}
}, // update部分
{
upsert:true
}
)
此时db中数据如下:
{ _id: ObjectId("626769601ab426660b7da987"),
site_id: 'a',
task_info: [ { task_id: '123', status: 1, operator: 112 } ] }
再执行一次该语句,db中数据不变,因为该对象已经存在
如果修改要添加的元素中某个字段:
db.site_task.updateOne(
{
site_id:"a"
}, // filter部分
{
$addToSet:
{
task_info:{
task_id:"124",
status:1,
operator:112
}
}
}, // update部分
{
upsert:true
}
),{upsert:true})
该字段就会有两个元素:
{ _id: ObjectId("626769601ab426660b7da987"),
site_id: 'a',
task_info:
[ { task_id: '123', status: 1, operator: 112 },
{ task_id: '124', status: 1, operator: 112 } ] }
如果使用push,就会无条件往后增加元素
这两个操作能保证原子性,不会发生更新丢失的问题,更多原子性操作可以参考MongoDB 原子操作
原子追加操作配合upsert,就能实现没有记录就新建,否则往记录某个字段增加,且不会更新丢失的效果
若db选型为mysql,可以使用on duplicate key update,如果不存在就添加,如果存在就修改,且自动实现了遇到重复key就改为更新操作,示例如下:
insert into table(field1,field2,field3) values(1,2,3) on duplicate key update field1=11,field2=22,field3=33
本文介绍了如何优雅实现没有就新增,否则更新,且在数组字段上实现原子追加的方法