背景
工作中经常会遇到一些场景,需要DB中已存在某行记录时进行更新,如果不存在的话则进行新增插入。
方案对比
方案 | 实现 | 优点 | 缺点 | 说明 |
---|---|---|---|---|
1 | insert … on DUPLICATE key update | 更新和新增在一个事务,且支持单条或者批量 | 无论是否新增,只要触发就会使主键seq值自增,受主键的最大长度限制 | 可使用bigint类型主键,最大表示19位,结合业务调用方控制必要时才触发此操作(如果下单置为老用户时先查询判断是老用户就不更新了),避免不必要的调用。但这个比较强依赖调用方。 |
2 | 同一事务内:update + insert (update无变更行时表示无数据,进行新增) | 单条操作简单,并发安全,较好地避免无效调用带来的id自增问题 | 批量实现不友好,批量更新时无法感知哪些更新成功哪些更新失败;悲观锁,效率较低。 | 目前能做的:1. 利用mysql url的allowMultiQueries=true属性实现一次批量更新。2.批量插入。但无法准确对更新无记录的数据进行新增,要么就是代码中循环单条执行『更新+插入』。 |
3 | 一锁二判三更新。同一事务内,select … for update查询加行锁,之后进行更新或者插入 | 并发安全;根据查询结果可以知道哪些记录不存在需要插入,哪些记录存在需要更新。 | 悲观锁,效率较低 | – |
4 | 乐观锁:普通查询+插入或者利用版本号判断更新 | 并发效率高且安全 | 版本号更新失败需要重试,需要额外成本;例如重试3次,同时10个请求,最多只会成功3个,太多请求写同一数据一定有问题,拦截掉可行;版本号上限问题 | 1.版本号更新失败的重试处理:同一用户对同一bizType的同一bizVal同时进行更新的概率不大,即用户不太可能同时在不同设备下相同的订单。2.版本号只有在更新成功时才会进行+1,这个上限由单个用户在同一店铺下的订单数决定,目前smallint类型,最大支持2^15-1(32767),结合目前业务量来讲够用,并且达到最大值更新失败后也不影响用户身份标识(三万多单一定是老用户了)。3.某条记录的版本号达到上限,也只会影响当前用户,不会像id一样影响所有数据 |
可以根据具体场景选择合适的方案,如有其他更好的方案,欢迎评论交流~