异步读取Spring事务还未提交的数据,你确定能读取的到?
后台回复如下关键字,获取相关资料
0101-开发软件工具大礼包
傍晚五点出头是最美好的时刻,因为离下班不远了~
然鹅,老大一个消息把我拉近群里,协助排查问题。一个同事查了快一天了,也不清楚问题出现在哪里。
入会,boss、架构师、同事正襟危坐,开始说现象:
有一笔下发给其他系统的数据中,是更新之前的数据,而数据库显示的却是更新之后的数据。
而同事也斩钉截铁的说:是更新之后再查询数据发给其他系统的,不可能还是更新之前的数据。
查日志、看MQ消息,捞MongoDB,最终由日志文件中找到的 SELECT 语句打印的日志确实是更新之前的数据。
直接拉代码看,不看不知道,一看就出事(因涉密我这里列出的是模拟的代码):
@Service
public class UserService {
@Autowired
private UserCellService userCellService;
@Autowired
private MqTemplate mqTemplate;
/**
* 更新用户
*/
public void updateUser(UserVO userVO) {
// 创建用户
userCellService.update(userVO);
// 异步下发数据
sendData(userVO.getUserId());
}
/**
* 异步推送数据
*/
private void sendData(Long userId) {
CompletableFuture.runAsync(() -> {
UserVO userVO = userCellService.findUserById(userId);
mqTemplate.send(userVO);
});
}
}
@Service
public class UserCellService {
@Transactional
public void updateUser(UserVO userVO) {
// 1.校验用户合法性
validateUser(userVO);
// 2.新增用户
userDao.updateUser(userVO);
}
}
调用流程:controller类 --> UserService.updateUser --> UserCellService.updateUser --> UserService.sendData
项目使用声明式事务,会将以 Service 为后缀的方法纳入事务管理。
实际的代码比上面的复杂,但调用流程是差不多的,我定睛一看:出事了。
内心保持冷静,再推敲下:真出事了。
分析结果:
controller 调用 UserService.updateUser 方法的时候已经开启 Spring 事务,进入到 UserCellService.updateUser 方法时,也只是沿用了上一层的事务而已
退出 UserCellService.updateUser 方法时,数据其实还没真正 commit 到数据库,因为 UserService.updateUser 整个方法还未退出
这时,又启动了一个子线程去查询主线程中还未提交到数据库的数据,当子线程处理速度快过主线程,主线程还未commit,这时候子线程查到的就是之前旧的数据
上面出现的问题,测试人员是基本测不出来的,因为这是偶发现象。
这种问题在工作中还是经常会出现的,Spring事务、异步处理要非常小心这种情况。对应的解决方案其实也有:
在外层将整个 VO 传到子线程
等待主线程commit完毕,再处理子线程逻辑(这个SpringBoot有提供一个事务提交异步监听机制,很方便,后续我会写一篇文章说明)