项目代码基于:MySql 数据,开发框架为:SpringBoot、Mybatis
开发语言为:Java8
前言
公司业务中遇到一个需求,需要同时修改最多约5万条数据,而且还不支持批量或异步修改操作。于是只能写个for循环操作,但操作耗时太长,只能一步一步寻找其他解决方案。
具体操作如下:
一、循环操作的代码
先写一个最简单的for循环代码,看看耗时情况怎么样。
/*** * 一条一条依次对50000条数据进行更新操作 * 耗时:2m27s,1m54s */ @Test void updateStudent() { List<Student> allStudents = studentMapper.getAll(); allStudents.forEach(s -> { //更新教师信息 String teacher = s.getTeacher(); String newTeacher = "TNO_" + new Random().nextInt(100); s.setTeacher(newTeacher); studentMapper.update(s); }); }
循环修改整体耗时约 1分54秒,且代码中没有手动事务控制应该是自动事务提交,所以每次操作事务都会提交所以操作比较慢,我们先对代码中添加手动事务控制,看查询效率怎样。
最新面试题整理:https://www.javastack.cn/mst/
二、使用手动事务的操作代码
修改后的代码如下:
@Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; /** * 由于希望更新操作 一次性完成,需要手动控制添加事务 * 耗时:24s * 从测试结果可以看出,添加事务后插入数据的效率有明显的提升 */ @Test void updateStudentWithTrans() { List<Student> allStudents = studentMapper.getAll(); TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); try { allStudents.forEach(s -> { //更新教师信息 String teacher = s.getTeacher(); String newTeacher = "TNO_" + new Random().nextInt(100); s.setTeacher(newTeacher); studentMapper.update(s); }); dataSourceTransactionManager.commit(transactionStatus); } catch (Throwable e) { dataSourceTransactionManager.rollback(transactionStatus); throw e; } }
添加手动事务操控制后,整体耗时约 24秒,这相对于自动事务提交的代码,快了约5倍,对于大量循环数据库提交操作,添加手动事务可以有效提高操作效率。
三、尝试多线程进行数据修改
添加数据库手动事务后操作效率有明细提高,但还是比较长,接下来尝试多线程提交看是不是能够再快一些。
先添加一个Service将批量修改操作整合一下,具体代码如下:
StudentServiceImpl.java
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; @Override public void updateStudents(List<Student> students, CountDownLatch threadLatch) { TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); System.out.println("子线程:" + Thread.currentThread().getName()); try { students.forEach(s -> { // 更新教师信息 // String teacher = s.getTeacher(); String newTeacher = "TNO_" + new Random().nextInt(100); s.setTeacher(newTeacher); studentMapper.update(s); }); dataSourceTransactionManager.commit(transactionStatus); threadLatch.countDown(); } catch (Throwable e) { e.printStackTrace(); dataSourceTransactionManager.rollback(transactionStatus); } } }
批量测试代码,我们采用了多线程进行提交,修改后测试代码如下:
@Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; @Autowired private StudentService studentService; /** * 对用户而言,27s 任是一个较长的时间,我们尝试用多线程的方式来经行修改操作看能否加快处理速度 * 预计创建10个线程,每个线程进行5000条数据修改操作 * 耗时统计 * 1 线程数:1 耗时:25s * 2 线程数:2 耗时:14s * 3 线程数:5 耗时:15s * 4 线程数:10 耗时:15s * 5 线程数:100 耗时:15s * 6 线程数:200 耗时:15s * 7 线程数:500 耗时:17s * 8 线程数:1000 耗时:19s * 8 线程数:2000 耗时:23s * 8 线程数:5000 耗时:29s */ @Test void updateStudentWithThreads() { //查询总数据 List<Student> allStudents = studentMapper.getAll(); // 线程数量 final Integer threadCount = 100; //每个线程处理的数据量 final Integer dataPartionLength = (allStudents.size() + threadCount - 1) / threadCount; // 创建多线程处理任务 ExecutorService studentThreadPool = Executors.newFixedThreadPool(threadCount); CountDownLatch threadLatchs = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { // 每个线程处理的数据 List<Student> threadDatas = allStudents.stream() .skip(i * dataPartionLength).limit(dataPartionLength).collect(Collectors.toList()); studentThreadPool.execute(() -> { studentService.updateStudents(threadDatas, threadLatchs); }); } try { // 倒计时锁设置超时时间 30s threadLatchs.await(30, TimeUnit.SECONDS); } catch (Throwable e) { e.printStackTrace(); } System.out.println("主线程完成"); }
多线程提交修改时,我们尝试了不同线程数对提交速度的影响,具体可以看下面表格,
多线程修改50000条数据时 不同线程数耗时对比(秒)
根据表格,我们线程数增大提交速度并非一直增大,在当前情况下约在2-5个线程数时,提交速度最快(实际线程数还是需要根据服务器配置实际测试)。
另外,MySQL 系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。