首页 > 基础资料 博客日记

Java: 并发情况下,数据插入重复(唯一约束+重试)

2023-07-25 19:16:18基础资料围观422

本篇文章分享Java: 并发情况下,数据插入重复(唯一约束+重试),对你有帮助的话记得收藏一下,看Java资料网收获更多编程知识

背景

假设同步库存的业务逻辑以SKU Code为唯一标识,存在两种情况

  1. 表中不存在,insert
  2. 表中存在,update

一开始没有仔细考虑有什么异常case,直接拿起来就写

@Transactional(rollbackFor = Exception.class)
public void execute(){
  ProductStock resultStock = gateway.getStockBySkuCode(skuCode);
  // 禁止重复插入
  if (resultStock == null) {
      resultStock = tempStock;
      gateway.insertStock(resultStock);
  } else {
    resultStock.updateStock(cmd.getQty(), cmd.getType());
    gateway.updateStock(resultStock);
  }
}

现象

当重复的请求同时执行
image

  • 导致重复数据,影响后续服务

最终期望

image

方案一

  1. 添加唯一约束(UNIQUE)
    为SKU Code添加唯一约束,这样能保证不出现重复插入的情况
  2. 考虑幂等性
    程序会抛出DuplicateKeyException,这个时候为了保证幂等性考虑在catch里完成update
@Transactional(rollbackFor = Exception.class)
public void execute(){
  ProductStock resultStock = gateway.getStockBySkuCode(skuCode);
  // 禁止重复插入
  if (resultStock == null) {
    // 简单幂等处理
    try {
      resultStock = tempStock;
      gateway.insertStock(resultStock);
    } catch (DuplicateKeyException exception) {
      // 重新获取DBstock
      resultStock = gateway.getStockBySkuCode(skuCode);
      resultStock.updateStock(cmd.getQty(), cmd.getType());
      // 更新stock
      gateway.updateStock(resultStock);
      // throw new BizException(INVENTORY_UPDATE_FAILED);
    }
  } else {
    resultStock.updateStock(cmd.getQty(), cmd.getType());
    gateway.updateStock(resultStock);
  }
}
  1. 分析步骤二失败原因
    原本的想法是捕获到抛出的Duplicate Exception,然后完成Update操作,但经过测试发现并没有达到预期效果。

    • 怀疑是加了Transactional注解的原因
      复习一下数据库的四种隔离级别

      读未提交(READ UNCOMMITTED):在这种隔离级别下,可能会出现脏读、不可重复读、幻读问题。
      读提交 (READ COMMITTED):解决脏读问题。
      可重复读 (REPEATABLE READ):解决脏读、不可重复读问题。
      串行化 (SERIALIZABLE):解决脏读、不可重复读、幻读问题。

      顺带复习一下这三种问题现象

      1.脏读(读取未提交的数据)
      脏读又称无效数据的读出,是指在数据库访问中,事务 A 对一个值做修改,事务 B 读取这个值,但是由于某种原因事务 A 回滚撤销了对这个值得修改,这就导致事务 B 读取到的值是无效数据。

      2、不可重复读(前后数据多次读取,结果集内容不一致)
      不可重复读即当事务 A 按照查询条件得到了一个结果集,这时事务 B 对事务 A 查询的结果集数据做了修改操作,之后事务 A 为了数据校验继续按照之前的查询条件得到的结果集与前一次查询不同,导致不可重复读取原始数据。

      3、幻读(前后数据多次读取,结果集数量不一致)
      幻读是指当事务 A 按照查询条件得到了一个结果集,这时事务 B 对事务 A 查询的结果集数据做新增操作,之后事务 A 继续按照之前的查询条件得到的结果集平白无故多了几条数据,好像出现了幻觉一样。

    • 各个数据库默认隔离级别

      • Oracle 读已提交(Read Commited)
      • SqlServer 读已提交(Read Commited)
      • MySQL 5.5+可重复读(Repeatable Read)

      因为MySQL5.5后默认使用InnoDB存储引擎

    有了上面基础知识的铺垫,这下问题就显而易见了,因为隔离级别是RR,所以在这个事务中就算catch Exception再重新Select DB得到的结果还是一样的,不然就是出现了不可重复的问题,所以这个方案很糟糕,排除。

  2. 事务重试
    由于还是没有达到最终期望,在不改变事务隔离级别的前提下,数据库使用的是乐观锁,用version作为更新依据,考虑进行重试事务操作

  • 考虑使用自定义注解+AOP切面来进行重试事物操作
    image
  • 根据阿里巴巴开发手册自定义重试次数
    image

至此最终期望以达成


文章来源:https://www.cnblogs.com/tanhaoo/p/16498792.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云