当前位置: 首页 > news >正文

在高并发场景下,仅依赖数据库机制(如行锁、版本控制)无法完全避免数据异常的问题

在面试的过程中,会经常遇到面试官询问:
1、为什么要订单的新增\修改前要增加分布式锁?
不是可以通过联合唯一索引控制重复了嘛?可以通过版本控制并发修改嘛?或者直接行锁或者间隙锁?
我解释了一遍,他们也不信,觉得网上的解决方案就是对的,所以我这里用实际的代码运行结果,解释一遍原因,不信的人可以仿造代码自己执行试试

  • 1、背景
  • 2、一个数据库出现异常问题的代码示例
  • 3、出现这个数据异常问题的原因
  • 4、如何防止出现这种情况

1、背景

在做ToB业务系统时,出现长事务时无可避免的,所以你会发现很多时候在针对某一个订单数据做新增和修改时,都会提前加一个分布式的锁(缓存锁,注册锁都可以),

这里你可能就有疑问了,新增为什么不靠数据库表的联合唯一索引做限制呢?修改为什么不用version字段做版本控制呢?
带着这些问题,往下看一个异常数据的代码示例

2、一个数据库出现异常问题的代码示例

 @Testpublic void test() throws InterruptedException {// 创建两个线程对象MyThread thread1 = new MyThread(supplierInfoService,"1111");MyThread thread2 = new MyThread(supplierInfoService,"2222");// 启动线程thread1.start();Thread.sleep(500);thread2.start();}

下面是线程的内部实现

 class MyThread extends Thread {private String creatName;private SupplierInfoService supplierInfoService;public MyThread(SupplierInfoService supplierInfoService,String creatName) {this.supplierInfoService = supplierInfoService;this.creatName = creatName;}@Overridepublic void run() {try {supplierInfoService.test123(creatName);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}   

下面是supplierInfoService 这个接口的test123方法, 在方法也增加了事务注解 @Transactional

volatile int num=0;@Transactional(rollbackFor = Exception.class)@Overridepublic void test123(String createName) throws InterruptedException {SupplierInfoDto supplierInfoDto=supplierInfoDomain.getById(1901453933037268992L);log.error("3333"+createName+JSONObject.toJSONString(supplierInfoDto));num++;while(num<2){}if(createName.equals("2222")){Thread.sleep(500);}supplierInfoDomain.updateCreatUserName(1901453933037268992L,"admin",createName);SupplierInfoDto supplierInfoDto1=supplierInfoDomain.getById(1901453933037268992L);log.error("4444"+createName+JSONObject.toJSONString(supplierInfoDto1));}

SQL如下

 <update id="updateCreatUserName">UPDATE sup_supplier_infoSET create_by=#{newCreatUserName,jdbcType=VARCHAR}WHERE id=#{id,jdbcType=BIGINT} and create_by=#{creatUserName};</update>

具体的流程:

  1. 创建两个线程,第一个线程先执行,第二个线程等待500毫秒
  2. 当第一个线程根据事务创建了readview(1)后原地自旋,等待第二个线程根据事务创建readview(2)
  3. 之后线程2休眠500ms,线程1执行update
  4. 之后线程2再执行update

这里有一个问题,最后create_by的最后结果是什么?

答案:如下
在这里插入图片描述

是不是很意外,以为通过where已经过滤了,那第二次修改应该不成功的,实际是成功的

3、出现这个数据异常问题的原因

原因也很简单,就是事务的第一次select 生成了readview,两个事务都有各自的readview,
虽然update默认有行锁,但是那是写,不是查,为了顺序写的,而在update语句中,where条件的作用域是在当前事务的readview上,只有set是作用在实际数据上

4、如何防止出现这种情况

通过上面的代码示例也清楚,通过version字段是无法解决问题这个问题的,加行锁也没有用(update默认行锁,看下面的锁的解释,只是为了其他事务的修改和删除,对于查询是不起作用的),
所以还是要提前加分布式锁,

行锁(Record Lock)​​
​​触发条件​​:当 UPDATE 语句的 WHERE 条件命中 ​​索引列​​(尤其是唯一索引或主键)时,InnoDB 会对符合条件的行加 ​​排他行锁(X Lock)​​,阻止其他事务修改或删除该行。

间隙锁(Gap Lock)​​
​​触发条件​​:当 WHERE 条件未命中索引或涉及范围查询时,InnoDB 会加 ​​间隙锁(Gap Lock)​​,锁定索引记录之间的间隙,防止其他事务插入新数据。

http://www.lryc.cn/news/587369.html

相关文章:

  • 用豆包AI云盘保存和分析录音文件
  • 维基艺术图片: 数据标注 (2)
  • java: DDD using oracle 21c
  • 树莓派5-ollama-linux-arm64.tgz 下载
  • KL散度:信息差异的量化标尺 | 从概率分布对齐到模型优化的核心度量
  • 强化学习初探及OREAL实践
  • Leaflet面试题及答案(61-80)
  • Flink数据流高效写入MySQL实战
  • XCZU2CG-2SFVC784I Xilinx FPGA AMD Zynq UltraScale+ MPSoC
  • Vivado ILA抓DDR信号(各种IO信号:差分、ISERDES、IOBUFDS等)
  • 六、深度学习——NLP
  • 无缝衔接直播流体验
  • 早期 CNN 的经典模型—卷积神经网络(LeNet)
  • 板凳-------Mysql cookbook学习 (十一--------8)
  • 【深度学习新浪潮】什么是新视角合成?
  • STM32-第五节-TIM定时器-1(定时器中断)
  • JAVA并发——synchronized的实现原理
  • 特征选择方法
  • 一文打通MySQL任督二脉(事务、索引、锁、SQL优化、分库分表)
  • GraphRAG Docker化部署,接入本地Ollama完整技术指南:从零基础到生产部署的系统性知识体系
  • AEC线性处理
  • 【iOS】方法与消息底层分析
  • 【设计模式】命令模式 (动作(Action)模式或事务(Transaction)模式)宏命令
  • phpMyAdmin:一款经典的MySQL在线管理工具又回来了
  • 【RA-Eco-RA6E2-64PIN-V1.0 开发板】ADC 电压的 LabVIEW 数据采集
  • 第一个Flink 程序 WordCount,词频统计(批处理)
  • git实操
  • 鸿蒙项目构建配置
  • 区分三种IO模型和select/poll/epoll
  • Java设计模式之行为型模式(命令模式)