一、背景
业务处理过程,发现了以下问题,代码一是原代码能正常执行,代码二是经过迭代一次非正常执行代码
代码一:以下代码开启线程后,代码正常执行
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5)); @Transactional public Long test() { // ...... // 插入记录 Long studentId = studentService.insert(student); // 异步线程 writeStatisticsData(studentId); return studentId; } private void writeStatisticsData(Long studentId) { executor.execute(() -> { Student student = studentService.findById(studentId); //........ }); }
代码二:以下代码开启线程后,代码不正常执行
@Transactional public Long test() { // ...... // 插入记录 Long studentId = studentService.insert(student); // 异步线程 writeStatisticsData(studentId); // 插入学生地址记录 Long addressId = addressService.insert(address); return studentId; } private void writeStatisticsData(Long studentId) { executor.execute(() -> { Student student = studentService.findById(studentId); //........ }); }
二、问题分析
这里使用了spring事务,显然需要考虑事务的隔离级别
2.1、mysql隔离级别
查看mysql隔离级别
SELECT @@tx_isolation; READ-COMMITTED
读提交,即在事务A插入数据过程中,事务B在A提交之前读取A插入的数据读取不到,而B在A提交之后再去读就会读取到A插入的数据,也即Read Committed不能保证在一个事务中每次读都能读到相同的数据,因为在每次读数据之后其他并发事务可能会对刚才读到的数据进行修改。
2.2、问题原因分析
代码一正常运行的原因
由于mysql事务的隔离级别是读提交,test方法在开启异步线程后,异步线程也开启了事务,同时以读者身份去读 test 方法中插入的 student 记录,但此时 test 方法已经提交了事务,所以可以读取到 student 记录(即在异步方法中可以读取到 student 记录),但此代码有风险,若事务提交的时间晚一点,异步线程也有可能读取不到 student 记录。
代码二不能正常运行的原因
经过上面分析,很明显异步方法中不能读取到 student 记录,由于代码二在异步线程下面又执行了其他操作,延时了test方法中事务的提交,所以代码二不能正常运行。