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

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

背景

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

表中不存在,insert

表中存在,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);

}

}

现象

当重复的请求同时执行

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

最终期望

方案一

添加唯一约束(UNIQUE)

为SKU Code添加唯一约束,这样能保证不出现重复插入的情况

考虑幂等性

程序会抛出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);

}

}

分析步骤二失败原因

原本的想法是捕获到抛出的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得到的结果还是一样的,不然就是出现了不可重复的问题,所以这个方案很糟糕,排除。

事务重试

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

考虑使用自定义注解+AOP切面来进行重试事务操作

根据阿里巴巴开发手册自定义重试次数

至此最终期望以达成

相关推荐

鲁大师跑分都会检测哪些硬件
金士顿a400评测 让老笔记本全新复活体验
钓鱼玉米怎么挂钩最好?真的有最好吗?适合的可能才是正确的
喜欢鹿晗的63个理由,欢迎您补充!
硬盘的基本知识与选购指南
敲击兽 - 动物
浅谈一下今年库存香菜的看法
淘宝卖家出售商品后多久能到账?为何需要等待45天?
如何隐藏电脑连接的WiFi密码