多线程批量插入数据,3s插入20w数据,大数据操作必备

 一、数据库链接设置

jdbc:mysql://数据库地址/数据库名?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true&rewriteBatchedStatements=true

rewriteBatchedStatements=false情况下,且MP(Mybaties-Plus)为3.5.3.1版本下进行测试的。 

二、操作方法

使用多线程方式更新数据,首先我们先测试使用5个线程插入20万条数据,使用Mybaties-plus自带的saveBatch()方法更新,准备20w的数据,然后创建线程池,五个线程池批量跑

直接上代码:

import org.jeecg.JeecgSystemApplication;
import org.jeecg.modules.demo.test.entity.TestUser;
import org.jeecg.modules.demo.test.mapper.TestUserMapper;
import org.jeecg.modules.demo.test.service.ITestUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = JeecgSystemApplication.class)
public class UserTest {

    @Autowired
    private ITestUserService userService;

    @Autowired
    private TestUserMapper testUserMapper;

     @Test
    public void testInsertBatchMulThreadSaveBatch() throws Exception{
        int totalRecords = 199999;
        int batchSize = 5000;
        int threadCount = 5; // 可以根据实际情况调整线程数量

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        List<Future<Void>> futures = new ArrayList<>();

        long s = System.currentTimeMillis();
        for (int i = 0; i < totalRecords; i += batchSize) {
            int startIndex = i;
            int endIndex = Math.min(i + batchSize, totalRecords);

            List<TestUser> batchList = new ArrayList<>();
            for (int j = startIndex; j < endIndex; j++) {
                TestUser user = new TestUser();
                user.setName("张三");
                user.setAge("20");
                user.setProvince("重庆市");
                user.setSalary("200000");
                user.setRemark("diitch");
                batchList.add(user);
            }

            Future<Void> future = executor.submit(() -> {
                userService.saveBatch(batchList);
                return null;
            });
            futures.add(future);
        }

        // 等待所有线程执行完成
        for (Future<Void> future : futures) {
            future.get();
        }

        executor.shutdown();
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
    }
}

三、优化

将二十万条数据一次性放入内存中,确实有可能导致内存溢出(OOM,Out of Memory)。为了优化这段代码,我们可以考虑以下几个方面:

  1. 减少内存使用:避免一次性创建所有对象,而是分批次创建并处理。

  2. 优化数据插入逻辑:使用数据库事务和批量插入,减少数据库交互次数。

  3. 合理配置线程池:根据系统资源合理配置线程池大小。

  4. 异步处理:使用异步编程模型,提高数据处理效率。

  5. 资源清理:及时释放不再使用的资源。

下面是优化后的代码示例:

import org.jeecg.JeecgSystemApplication;
import org.jeecg.modules.demo.test.entity.TestUser;
import org.jeecg.modules.demo.test.mapper.TestUserMapper;
import org.jeecg.modules.demo.test.service.ITestUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.*;
import java.util.*;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = JeecgSystemApplication.class)
@EnableAsync // 开启异步支持
public class UserTest {

    @Autowired
    private ITestUserService userService;

    @Autowired
    private TestUserMapper testUserMapper;

    @Test
    public void testInsertBatchMulThreadSaveBatch() throws Exception {
        int totalRecords = 200000;
        int batchSize = 5000;
        int threadCount = 5; // 可以根据实际情况调整线程数量

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount); // 用于等待所有线程完成

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < totalRecords; i += batchSize) {
            int startIndex = i;
            int endIndex = Math.min(i + batchSize, totalRecords);

            executor.submit(() -> {
                try {
                    List<TestUser> batchList = new ArrayList<>();
                    for (int j = startIndex; j < endIndex; j++) {
                        TestUser user = new TestUser();
                        user.setName("张三");
                        user.setAge("20");
                        user.setProvince("重庆市");
                        user.setSalary("200000");
                        user.setRemark("diitch");
                        batchList.add(user);
                    }
                    userService.saveBatch(batchList); // 假设这个方法支持批量插入
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 计数减一,表示一个线程任务完成
                }
            });
        }

        latch.await(); // 等待所有线程执行完成
        executor.shutdown();
        long endTime = System.currentTimeMillis();
        System.out.println("保存" + totalRecords + "条数据消耗" + (endTime - startTime) + "ms");
    }
}

请注意,这个示例代码假设ITestUserServicesaveBatch方法已经支持批量插入操作,并且能够处理传入的List<TestUser>。如果saveBatch方法不支持批量插入,你可能需要修改服务层的实现,或者在TestUserMapper中实现批量插入逻辑。

此外,使用CountDownLatch代替Future可以简化线程同步的代码,并避免处理Future可能抛出的异常。这样可以更清晰地管理线程的生命周期,并在所有任务完成后统一进行资源清理。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java斌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值