MyBatis批量插入优化的方法步骤

 更新时间:2026年02月11日 08:21:29   作者:花宝宝hua  
本文主要介绍了MyBatis批量插入优化的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

上周接了个数据迁移的活,要把10万条数据从老系统导入新系统。

写了个简单的批量插入,跑起来一看——5分钟

领导说太慢了,能不能快点?

折腾了一下午,最后优化到3秒,记录一下过程。

最初的代码(5分钟)

最开始写的很简单,foreach循环插入:

// 方式1:循环单条插入(最慢)
for (User user : userList) {
    userMapper.insert(user);
}

10万条数据,每条都要走一次网络请求、一次SQL解析、一次事务提交。

算一下:假设每条插入需要3ms,10万条就是300秒 = 5分钟。

这是最蠢的写法,但我见过很多项目都这么写。

第一次优化:批量SQL(30秒)

把循环插入改成批量SQL:

<!-- Mapper.xml -->
<insert id="batchInsert">
    INSERT INTO user (name, age, email) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.age}, #{item.email})
    </foreach>
</insert>
// 分批插入,每批1000条
int batchSize = 1000;
for (int i = 0; i < userList.size(); i += batchSize) {
    int end = Math.min(i + batchSize, userList.size());
    List<User> batch = userList.subList(i, end);
    userMapper.batchInsert(batch);
}

从5分钟降到30秒,提升10倍。

原理:一条SQL插入多条数据,减少网络往返次数。

但还有问题:30秒还是太慢。

第二次优化:JDBC批处理(8秒)

MySQL有个参数叫rewriteBatchedStatements,开启后可以把多条INSERT合并成一条。

第一步:修改数据库连接URL

jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true

第二步:使用MyBatis的批处理模式

@Autowired
private SqlSessionFactory sqlSessionFactory;

public void batchInsertWithExecutor(List<User> userList) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        int batchSize = 1000;
        for (int i = 0; i < userList.size(); i++) {
            mapper.insert(userList.get(i));
            
            if ((i + 1) % batchSize == 0) {
                sqlSession.flushStatements();
                sqlSession.clearCache();
            }
        }
        sqlSession.flushStatements();
        sqlSession.commit();
    }
}

从30秒降到8秒

原理ExecutorType.BATCH模式下,MyBatis会缓存SQL,最后一次性发送给数据库执行。配合rewriteBatchedStatements=true,MySQL驱动会把多条INSERT合并。

第三次优化:多线程并行(3秒)

8秒还是不够快,上多线程:

public void parallelBatchInsert(List<User> userList) {
    int threadCount = 4;  // 根据数据库连接池大小调整
    int batchSize = userList.size() / threadCount;
    
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    List<Future<?>> futures = new ArrayList<>();
    
    for (int i = 0; i < threadCount; i++) {
        int start = i * batchSize;
        int end = (i == threadCount - 1) ? userList.size() : (i + 1) * batchSize;
        List<User> subList = userList.subList(start, end);
        
        futures.add(executor.submit(() -> {
            batchInsertWithExecutor(subList);
        }));
    }
    
    // 等待所有任务完成
    for (Future<?> future : futures) {
        try {
            future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    executor.shutdown();
}

从8秒降到3秒

注意事项

  1. 线程数不要超过数据库连接池大小
  2. 如果需要事务一致性,这个方案不适用
  3. 要考虑主键冲突的问题

优化效果对比

方案耗时提升倍数
循环单条插入300秒基准
批量SQL30秒10倍
JDBC批处理8秒37倍
多线程并行3秒100倍

踩过的坑

坑1:foreach拼接SQL过长

<foreach collection="list" item="item" separator=",">

如果一次插入太多条,SQL会非常长,可能超过max_allowed_packet限制。

解决:分批插入,每批500-1000条。

坑2:rewriteBatchedStatements不生效

检查几个点:

  1. URL参数是否正确:rewriteBatchedStatements=true
  2. 是否使用了ExecutorType.BATCH
  3. MySQL驱动版本是否太旧

坑3:自增主键返回问题

批量插入时想获取自增主键:

<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">

注意rewriteBatchedStatements=true时,自增主键返回可能有问题,需要升级MySQL驱动到8.0.17+。

坑4:内存溢出

10万条数据一次性加载到内存,可能OOM。

解决:分页读取 + 分批插入。

int pageSize = 10000;
int total = countTotal();
for (int i = 0; i < total; i += pageSize) {
    List<User> page = selectByPage(i, pageSize);
    batchInsertWithExecutor(page);
}

最终方案代码

@Service
public class BatchInsertService {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 高性能批量插入
     * 10万条数据约3秒
     */
    public void highPerformanceBatchInsert(List<User> userList) {
        if (userList == null || userList.isEmpty()) {
            return;
        }
        
        int threadCount = Math.min(4, Runtime.getRuntime().availableProcessors());
        int batchSize = (int) Math.ceil((double) userList.size() / threadCount);
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            int start = i * batchSize;
            int end = Math.min((i + 1) * batchSize, userList.size());
            
            if (start >= userList.size()) {
                latch.countDown();
                continue;
            }
            
            List<User> subList = new ArrayList<>(userList.subList(start, end));
            
            executor.submit(() -> {
                try {
                    doBatchInsert(subList);
                } finally {
                    latch.countDown();
                }
            });
        }
        
        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        executor.shutdown();
    }
    
    private void doBatchInsert(List<User> userList) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            for (int i = 0; i < userList.size(); i++) {
                mapper.insert(userList.get(i));
                
                if ((i + 1) % 1000 == 0) {
                    sqlSession.flushStatements();
                    sqlSession.clearCache();
                }
            }
            
            sqlSession.flushStatements();
            sqlSession.commit();
        }
    }
}

总结

优化点关键配置
批量SQLforeach拼接,分批1000条
JDBC批处理rewriteBatchedStatements=true + ExecutorType.BATCH
多线程线程数 ≤ 连接池大小

核心原则:减少网络往返 + 减少事务次数 + 并行处理。

到此这篇关于MyBatis批量插入优化的方法步骤的文章就介绍到这了,更多相关MyBatis批量插入优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何基于SpringMVC实现断点续传(HTTP)

    如何基于SpringMVC实现断点续传(HTTP)

    这篇文章主要介绍了如何基于SpringMVC实现断点续传(HTTP),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • SpringSecurity微服务实战之公共模块详解

    SpringSecurity微服务实战之公共模块详解

    这篇文章主要为大家介绍了SpringSecurity微服务实战之公共模块详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 一文掌握Spring Boot 日志文件

    一文掌握Spring Boot 日志文件

    日志是程序的重要组成部分,日志对于我们来说,最主要的用途就是排除和定位问题,这篇文章主要介绍了Spring Boot 日志文件,需要的朋友可以参考下
    2023-03-03
  • jdk环境配置Maven环境配置实践

    jdk环境配置Maven环境配置实践

    文章介绍了如何配置JDK和Maven环境变量,并通过命令验证配置是否成功,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-02-02
  • 浅谈JVM系列之从汇编角度分析NullCheck

    浅谈JVM系列之从汇编角度分析NullCheck

    在virtual call中执行nullcheck的时候,如果已经知道传递的参数是非空的。JIT会对代码进行优化吗?本文将详细介绍JVM系列之从汇编角度分析NullCheck。
    2021-06-06
  • 详解安装Eclipse Maven插件的几种方法

    详解安装Eclipse Maven插件的几种方法

    本篇文章主要介绍了详解安装Eclipse Maven插件的几种方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • SpringLDAP目录服务之LdapTemplate与LDAP操作方式

    SpringLDAP目录服务之LdapTemplate与LDAP操作方式

    本文将深入探讨Spring LDAP的核心概念、LdapTemplate的使用方法以及如何执行常见的LDAP操作,帮助开发者有效地将LDAP集成到Spring应用中,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • spark中使用groupByKey进行分组排序的示例代码

    spark中使用groupByKey进行分组排序的示例代码

    这篇文章主要介绍了spark中使用groupByKey进行分组排序的实例代码,本文通过实例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Java Eclipse中实现快速替换变量

    Java Eclipse中实现快速替换变量

    这篇文章主要介绍了Java Eclipse中实现快速替换变量,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java程序包不存在的两种解决方法

    Java程序包不存在的两种解决方法

    有时候我们在导入程序之后,系统会给出错误提示:Java:程序包xxxx不存在,本文主要介绍了Java程序包不存在的两种解决方法,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02

最新评论