事务+锁?锁+事务?解析事务与锁的错综Bug
背景
我们的主要业务是台湾省的一个小商城,这次出问题的是我们「仓库系统」。在仓库系统中有这么一段逻辑:
员工可以领取「新建」的订单,然后去执行拣货发货的操作,领取的时候,发货单的状态会从「新建」变为「待拣货」,也就是说「找新建状态的发货单,领取然后变为待拣货然后去拣货」。
❞
bug内容
突然有一天,仓库的同事发消息说「有两位员工领取了同一个发货单」。拣货的时候报错该发货单已拣货。这可就奇了怪了,为了防止并发,且这个仓库是单节点部署的,记得是「加了锁的」。
模拟
这时候需要定位问题,先模拟一下我们的场景。
@ResponseBody
public void test(Pageable request){
for (int i = 0; i < 100; i++) {
//新建线程处理
new Thread(() -> {
userInfoService.testDemo();
}).start();
}
}
肯定是并发导致的,这里模拟一下高并发的情况
public synchronized void testDemo() {
UserInfo byUserId = userRepository.findByUserId(1);
byUserId.setAge(byUserId.getAge() + 1);
userRepository.save(byUserId);
}
可以看到业务逻辑里有个锁,并且有事务。
数据大概长这样,我拿之前我写的demo的表来处理,修改这个age 100次。
执行,不用看表了,看log就知道有问题的:

@GetMapping(path = “test”)
@ResponseBody
public void test(Pageable request) {
for (int i = 0; i < 100; i++) {
//新建线程处理
new Thread(() -> {
synchronized (UserController.class) {
userInfoService.testDemo();
}
}).start();
}
}
@Transactional(rollbackOn = Exception.class)
public void testDemo() {
UserInfo byUserId = userRepository.findByUserId(1);
log.info(“当前线程:{},当前年龄:{}”,Thread.currentThread().getName(),byUserId.getAge());
byUserId.setAge(byUserId.getAge() + 1);
userRepository.save(byUserId);
log.info(“当前线程:{},当前年龄:{}”,Thread.currentThread().getName(),byUserId.getAge());
}


又发生了
-
若是修改url的先获取发货单里所有「新建状态」的发货单 -
然后员工获取发货单数据,这时候因为修改url的线程需要调用api会稍微慢点 -
员工会把发货单修改为待拣货,然后保存入库 -
修改url的线程执行完了,由于我是执行的jpa的save方法,他会把自己读取到「新建状态」的数据,修改个url再保存回表,这时候,表里的数据又变成「新建状态」了。这样之后的员工又能取到这条发货单数据,然后这样不就重了。
微信赞赏
支付宝扫码领红包