为什么不建议MyBatis一次性批量插入几千条数据?

    问题引入

    近日,项目中有一个耗时较长的Job存在CPU占用过高的问题,经排查发现,主要时间消耗在往MyBatis中批量插入数据。mapper configuration是用foreach循环做的,差不多是这样。

    <insert id="batchInsert" parameterType="java.util.List">
        insert into USER (id, name) values
        <foreach collection="list" item="model" index="index" separator=","> 
            (#{model.id}, #{model.name})
        </foreach>
    </insert>
    

    这个方法提升批量插入速度的原理是,将传统的:

    INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
    INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
    INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
    INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
    INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
    

    转化为:

    INSERT INTO `table1` (`field1`, `field2`) 
    VALUES ("data1", "data2"),
    ("data1", "data2"),
    ("data1", "data2"),
    ("data1", "data2"),
    ("data1", "data2");
    

    在MySql Docs中也提到过这个trick,如果要优化插入速度时,可以将许多小型操作组合到一个大型操作中。理想情况下,这样可以在单个连接中一次性发送许多新行的数据,并将所有索引更新和一致性检查延迟到最后才进行。

    乍看上去这个foreach没有问题,但是经过项目实践发现,当表的列数较多(20+),以及一次性插入的行数较多(5000+)时,整个插入的耗时十分漫长,达到了14分钟,这是不能忍的。

    当插入数量很多时,不能一次性全放在一条语句里。可是为什么不能放在同一条语句里呢?这条语句为什么会耗时这么久呢?我查阅了资料发现:

    SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
    for (Model model : list) {
        session.insert("insertStatement", model);
    }
    session.flushStatements();
    Unlike default ExecutorType.SIMPLE, the statement will be prepared once and executed for each record to insert.
    

    从资料中知,默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PreparedStatement对象。在我们的项目中,会不停地使用批量插入这个方法,而因为MyBatis对于含有的语句,无法采用缓存,那么在每次调用方法时,都会重新解析sql语句。

    从资料可知,耗时就耗在,由于我foreach后有5000+个values,所以这个PreparedStatement特别长,包含了很多占位符,对于占位符和参数的映射尤其耗时。并且,查阅相关资料可知,values的增长与所需的解析时间,是呈指数型增长的。

    在这里插入图片描述

    所以,如果非要使用 foreach 的方式来进行批量插入的话,可以考虑减少一条 insert 语句中 values 的个数,最好能达到上面曲线的最底部的值,使速度最快。一般按经验来说,一次性插20~50行数量是比较合适的,时间消耗也能接受。

    重点来了。上面讲的是,如果非要用的方式来插入,可以提升性能的方式。而实际上,MyBatis文档中写批量插入的时候,是推荐使用另外一种方法。(可以看 http://www.mybatis.org/mybatis-dynamic-sql/docs/insert.html 中 Batch Insert Support 标题里的内容)

    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
        SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
        List<SimpleTableRecord> records = getRecordsToInsert(); // not shown
     
        BatchInsert<SimpleTableRecord> batchInsert = insert(records)
                .into(simpleTable)
                .map(id).toProperty("id")
                .map(firstName).toProperty("firstName")
                .map(lastName).toProperty("lastName")
                .map(birthDate).toProperty("birthDate")
                .map(employed).toProperty("employed")
                .map(occupation).toProperty("occupation")
                .build()
                .render(RenderingStrategy.MYBATIS3);
     
        batchInsert.insertStatements().stream().forEach(mapper::insert);
     
        session.commit();
    } finally {
        session.close();
    }
    

    即基本思想是将 MyBatis session 的 executor type 设为 Batch ,然后多次执行插入语句。就类似于JDBC的下面语句一样。

    Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true","root","root");
    connection.setAutoCommit(false);
    PreparedStatement ps = connection.prepareStatement(
            "insert into tb_user (name) values(?)");
    for (int i = 0; i < stuNum; i++) {
        ps.setString(1,name);
        ps.addBatch();
    }
    ps.executeBatch();
    connection.commit();
    connection.close();
    

    经过试验,使用了 ExecutorType.BATCH 的插入方式,性能显著提升,不到 2s 便能全部插入完成。

    总结
    如果MyBatis需要进行批量插入,推荐使用 ExecutorType.BATCH 的插入方式,如果非要使用 的插入的话,需要将每次插入的记录控制在 20~50 左右。

    本文作者:Java技术债务
    原文链接:https://www.cuizb.top/myblog/article/1642863288
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。

    更多文章和干货请移驾公众号和个人网站

    号外!号外!
    最近面试BAT,整理一份面试资料,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。想获取吗?如果你想提升自己,并且想和优秀的人一起进步,感兴趣的朋友,可以在扫码关注下方公众号。资料在公众号里静静的躺着呢。。。

    posted @   Java技术债务  阅读(103)  评论(0编辑  收藏  举报
    相关博文:
    阅读排行:
    · 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
    · 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
    · Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
    · 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
    · AI技术革命,工作效率10个最佳AI工具
    点击右上角即可分享
    微信分享提示
    💬
    评论
    📌
    收藏
    💗
    关注
    👍
    推荐
    🚀
    回顶
    收起
    1. 1 404 not found REOL
    404 not found - REOL
    00:00 / 00:00
    An audio error has occurred.