首页 > 其他分享 >存在则更新,不存在则插入的问题优化

存在则更新,不存在则插入的问题优化

时间:2023-07-20 15:57:48浏览次数:48  
标签:存在 success playerId void 插入 死锁 propId 优化 public

一、解决的场景

     开发中,经常遇到这样的场景,数据库中存在记录,则需要更新这条记录,不存在这条记录,则插入这条记录

     比如:给用户加积分,加道具,存在则直接字段加值,不存在这条记录需要插入初始化的一条数据; 统计每天的参与数(每天生成一条记录) 等等。

二、优化过程

1、先查再插

       最常见的编码方式:

   Entity entity = service.findById(10);

   if(null == entity)

       service.insert(obj);

    }else{

       service.update(obj);

    }

        问题: (1)要2次访问数据库。(2)有并发问题。

2、加同步锁

     先根据条件查询,存在就更新,不存在添加,但是往往我们是集群、多列的状态下,会再你正在判断null == entity的时候,另外一个线程已经插入完毕了,导致你以为是不存在,重复插入。

    在单实例中,可以用java锁解决, 在多实例中,可以通过分布式锁解决。

以单实例为例:

 synchronized(对象){

   Entity entity = service.findById(10);

   if(null == entity)

      service.insert(obj);

   }else{

      service.update(obj);

   }

   }

   问题:(1)要访问2次数据库,(2)如果加分布式锁,还需要多1次访问1次Redis,但性能很低。

   扩展:synchronized 粒度的问题,如:锁粒度一定要小,synchronized (("baiShi_lock" + activityId + currentPage).intern()) 

3、 INSERT 中 ON DUPLICATE KEY UPDATE的使用

在Mysql的语法中其实提供了这么一种操作,能够完成这样的任务。语法如下:
INSERT … ON DUPLICATE KEY UPDATE field1=value1, field2=value2,…;

这样的语句在执行时作为一个原子操作执行,如果数据库中没有该条记录,则插入,如果有,则更新这些字段(field1=value1, field2=value2,…, fieldn=valuesn);需要说明的是,数据库表必须含有一个唯一的索引,通过该索引来标记是否存在该条记录。

如:

INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;

不存在记录,则插入, 存在记录,则 UPDATE table SET c=c+1 WHERE a=1;

问题:有可能会死锁。 在5.7.30-log没有死锁问题。 在5.7.18-log  REPEATABLE-READ隔离级别下有死锁问题,在READ-COMMITTED隔离级别下没有死锁问题。

死锁问题:

https://www.pudn.com/news/628f8426bf399b7f351edd71.html

https://cloud.tencent.com/developer/article/1797058

https://www.likecs.com/show-305633666.html

https://blog.csdn.net/LS7011846/article/details/124548283

https://blog.csdn.net/agonie201218/article/details/125265266

 

4、唯一索引的方式

Boolean success = this.updateBackup(playerId,propId);
if(!success){
try {
// playerId 和 propId是组合唯一索引
this.insertBackup(playerId,propId);
 }catch (Exception ex){
this.updateBackup(playerId,propId);
}
}

需要注意的问题:(1)必须加唯一索引,通常情况下,只需要访问1次数据库,但插入时需要执行2到3次SQL。

                               (2)  如果在事务的情况下,性能较低(以下隔离级别为已提交读),还有可能导致死锁。

 

 

                                事务导致的死锁问题:

                                

 

 

参考:https://blog.csdn.net/weixin_44146398/article/details/125705284

以云逛店为例,公司的框架,隔离级别是已提交读, 如果加事务的话,主键重复可以捕获异常,更新成功了,但也会异常回滚,如下图。如果是死锁,连Exception和Error也捕获不到。

 

                           

5、高并发下,有事务导致回滚的解决方式

  解决方式:分布式锁  或  队列。

(1)分布式锁

加锁范围小的情况下:

 

问题:(1)有可能使分布式锁无法unlock。  

             (2)  依然有索引冲突和死锁的问题。

             

 

           索引冲突:隔离级别导致的

 

         死锁:第一个updateBackup与insertBackup导致的。

加锁范围大的情况下:

 

        问题:(1)有可能使分布式锁无法unlock。 

                   (2)性能很低。

(2)异常后发MQ,重新执行。

public Boolean saveBackup(Integer playerId, Integer propId){
//事务完成后提交
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
//doSomething
}
@Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
// 发MQ
}
});

// 更新背包
Boolean success = this.updateBackup(playerId,propId);
if(!success){
this.insertBackup(playerId, propDO);
}
return Boolean.TRUE;
}

 

复杂版本:

public Boolean saveBackup(Integer playerId, Integer propId){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
//doSomething
}
@Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
// 发MQ
}
}
});

Boolean success = this.updateBackup(playerId,propId);
if(!success){
PropDO propDO = propService.getById(propId);
try {
// playerId 和 propId是组合唯一索引
this.insertBackup(playerId, propDO);
}catch (Exception ex){
// 没有以下代码,在索引冲突的情况下,也不会发MQ,因为异常已经被捕获了。但有以下代码的话,就会发MQ,因为又接着抛出了死锁异常
//(奇怪的点:多次重启服务复测都是这个结果:9个主键冲突导致了8个死锁,触发了8次回滚,1次success为true,另外1次的死锁是刚进来执行updateBackup导致的)。
if(cause instanceof SQLIntegrityConstraintViolationException){
// log.info("重复主键");
success = this.updateBackup(playerId, propId);
// log.info("重复主键1,{}",success);
// }
}
}

}

 

优化版本1:

    public class TransactionUtil {
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(5,
20, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20), new ThreadPoolExecutor.AbortPolicy());

public static void afterCommit(Runnable runnable) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
try {
THREAD_POOL_EXECUTOR.execute(runnable);
} catch (Exception e) {
// 记录日志
}
}
});
}
}
// 调用就变成了
TransactionUtil.afterCommit(() -> System.out.println("测试事务提交后的逻辑处理"));

 

优化版本2:

利用Spring 4.2版本的新特性@TransactionalEventListener注解的事件机制来完成事务提交后的逻辑处理
@Autowired
private ApplicationEventPublisher publisher;

@Override
@Transactional(rollbackFor = Exception.class)
public void add(AdvanceChargeApplyAddInput input) {
// 业务逻辑TODO
// 发送事件
publisher.publishEvent(advanceChargeApply);
}
// 响应事件, 事务提交后执行
@TransactionalEventListener
public void handle(PayloadApplicationEvent<AdvanceChargeApply> event) {
System.out.println("TransactionalEventListener 事务提交后执行");
}

 

标签:存在,success,playerId,void,插入,死锁,propId,优化,public
From: https://www.cnblogs.com/maohuidong/p/17568606.html

相关文章

  • Idea SpringBoot 项目启动时提示程序包不存在和找不到符号
    从git上克隆了一个SpringBoot项目,并且使用Maven编译也通过了,奇怪的是当BuildProject时却提示符号不存在。如下图: 先查看导入的类是否存在,如果不存在的话,那查看一下是否缺少了maven依赖。我这边是可以访问到类的,并且jar包也导入成功了。 也尝试了网上的解决方法,设置Proj......
  • 批量插入图片
    Alt+F11或者在标签上右键查看代码---------------------------------------------SubPicturesInsert()Dimi,arr,str,typ,shpOnErrorResumeNext'忽略运行中可能出现的错误Application.ScreenUpdating=False'关闭工作表更新,提高运行速度Setmysheet1=ThisWork......
  • 特殊字体的优化
    背景在开发一个数据大屏项目中使用了阿里巴巴的普惠字体,其中最小的一个特殊字体的大小都超过2M(ε=(´ο`*)体积真是大啊)。这么大的体积导致项目发布到线上后单单特殊字体的体积就占总体积的50%以上,导致首次进入数据大屏首页速度特别慢(平均超过6s以上)。 解决方案 1、字......
  • jquery变量是否存在
    判断jQuery变量是否存在的流程为了判断一个jQuery变量是否存在,我们可以按照以下步骤进行操作:步骤描述1引入jQuery库2判断jQuery变量是否存在3根据判断结果进行相应的操作接下来,我将逐步解释每个步骤需要做什么,并提供相应的代码。步骤1:引入jQuery库首先,我......
  • mysql 插入语句
    MySQL插入语句MySQL是一种流行的开源关系型数据库管理系统,被广泛应用于各种应用程序和网站中。在使用MySQL时,插入语句是常用的操作之一,用于将数据添加到表中。本文将介绍MySQL中的插入语句的基本语法和使用示例,并提供一些实用的技巧和建议。基本语法MySQL中的插入语句使......
  • mysql 插入数据都在等待
    MySQL插入数据都在等待问题解析问题背景在使用MySQL数据库时,有时候会遇到插入数据的操作一直处于等待状态,导致业务无法正常进行的情况。这种情况可能导致数据库的性能下降,甚至影响整个系统的正常运行。本文将对这个问题进行分析,并提供相应的解决方案。问题原因MySQL插入数......
  • mysql 插入 自增id
    MySQL插入自增ID简介在MySQL数据库中,我们经常需要插入新的记录并自动生成唯一的自增ID。自增ID可以确保每个记录在插入时都具有唯一的标识符,通常用于作为主键或唯一标识符。本文将向你介绍如何在MySQL数据库中插入带有自增ID的记录。流程为了插入带有自增ID的记录,我们需要按照......
  • mysql 阻止数据插入
    如何阻止MySQL数据插入在MySQL中,有时候我们需要阻止某些数据被插入到数据库中,这可以通过使用触发器(Trigger)来实现。触发器是MySQL提供的一种特殊的存储过程,它会在指定的表上执行特定的操作,如插入、更新或删除数据。在本文中,我将教你如何使用触发器阻止MySQL数据插入。步骤概览下......
  • Vue利用element ui分栏,并将threejs插入到main
    目录1.在node.js官网下载2.在win+r中输入cmd打开管理员:命令提示符3.cmd命令下载安装淘宝镜像上的npm包4.cmd命令安装vuecli,并验证是否安装成功5.通过vueui命令以图形化界面创建和管理项目6.vue项目创建完成,在终端里安装elementui7.在main.js文件中引入element8.利用element进行......
  • 高频面试题 - 数据库优化方案
    一.考题再现最近很多小伙伴在跳槽面试,遇到了各种奇奇怪怪的问题。比如健哥的一个学生,在面试时被面试官问到如下问题:“我们做web开发都离不开http协议,那你了解http协议吗?”这时大家一般都是回答了解。然后面试官会接着对这个问题展开三连击,“Http协议是长连接还是短连接?......