服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

求求你们了,MyBatis 批量插入别再乱用 foreach 了,5000 条数据花了 14 分钟。。

日期: 来源:JAVA小咖秀收集编辑:

近日,项目中有一个耗时较长的Job存在CPU占用过高的问题,经排查发现,主要时间消耗在往MyBatis中批量插入数据。mapper configuration是用foreach循环做的,差不多是这样。(由于项目保密,以下代码均为自己手写的demo代码)

<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分钟,这是不能忍的。在资料中也提到了一句话:

Of course don't combine ALL of them, if the amount is HUGE. Say you have 1000 rows you need to insert, then don't do it one at a time. You shouldn't equally try to have all 1000 rows in a single query. Instead break it into smaller sizes.

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

Insert inside Mybatis foreach is not batch, this is a single (could become giant) SQL statement and that brings drawbacks:

  • some database such as Oracle here does not support.
  • in relevant cases: there will be a large number of records to insert and the database configured limit (by default around 2000 parameters per statement) will be hit, and eventually possibly DB stack error if the statement itself become too large.

Iteration over the collection must not be done in the mybatis XML. Just execute a simple Insertstatement in a Java Foreach loop. The most important thing is the session Executor type.

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语句。

Internally, it still generates the same single insert statement with many placeholders as the JDBC code above.

MyBatis has an ability to cache PreparedStatement, but this statement cannot be cached because it contains <foreach /> element and the statement varies depending on the parameters.
As a result, MyBatis has to 1) evaluate the foreach part and 2) parse the statement string to build parameter mapping [1] on every execution of this statement.
And these steps are relatively costly process when the statement string is big and contains many placeholders.

[1] simply put, it is a mapping between placeholders and the parameters.

从上述资料可知,耗时就耗在,由于我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 左右。

来源:blog.csdn.net/huanghanqian/article/details/83177178/

加小编微信,回复 40 白嫖40套 java/spring/kafka/redis/netty 教程/代码/视频 等


扫二维码,加我微信,回复:40

 注意,不要乱回复 

没错,不是机器人
记得一定要等待,等待才有好东西

相关阅读

  • 被问懵了:MySQL 自增主键一定是连续的吗?

  • 测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不
  • 光伏上市企业“大变脸”

  • ·会议推荐2023分布式光伏创新峰会(4.25~26 )参观整县分布式光伏项目(江苏连云港)咨询电话:18911205173(微信同号)继跨界潮后,A股市场又迎来了一大波“更名潮”。据统计,自2022年以来
  • 晶华新能源精彩亮相中国国际清洁能源博览会

  • ·会议推荐2023分布式光伏创新峰会(4.25~26 )参观整县分布式光伏项目(江苏连云港)咨询电话:18911205173(微信同号)来源: 晶华新能源3月28日2023中国国际清洁能源博览会在北京中国国
  • 中超公司原总经理董铮被查

  • 中超公司原总经理董铮接受监察调查中超联赛有限责任公司原总经理董铮涉嫌严重违法,目前正接受中央纪委国家监委驻国家体育总局纪检监察组和湖北省监委监察调查。来源:人民日报
  • 中国田协主席于洪臣被查

  • 中国田协主席于洪臣接受审查调查中国田径协会主席于洪臣涉嫌严重违纪违法,目前正接受中央纪委国家监委驻国家体育总局纪检监察组和湖北省监委审查调查。来源:人民日报微信公众
  • 孩子给离世父亲发短信,当晚竟收到回复!

  • 3月26日,陕西西安孩子给离世爸爸发短信当晚收到回复:“我的孩子是最棒的,爸爸也想你!”据@白鹿视频 消息,高先生就是回短信的“爸爸”。当他收到信息时还以为对方是发错了,但随后
  • √真的 | 军味相亲大会,甜甜甜……

  • 作者|周琪偲、刘洵、阳士教、马骏成、王定雄、孙洁康陆军第74集团军某旅“来部队谈一场甜甜的恋爱~”近日陆军第74集团军某旅携手驻地双拥办举办“爱在军营、情定一生”军地
  • Security and Safety (S&S) 2023年第一期目录

  • 扫描二维码或点击“阅读原文”,S&S内容全掌握Research ArticleInformation NetworkSecure transmission
    technology based on direct modulation with random channel chara

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • 同事竟然在悄悄模仿我的Spring用法...

  • 如今在java界,Spring可以说是大行其道。很多大厂选择Spring全家桶作为基础开发框架,无数的研发人员把Spring看作是最好的java项目,现在的java开发方面,Spring的重要性和影响力是
  • 谷歌宣布,所有芯片设计都已经上云

  • 来源:内容由半导体行业观察(ID:icbank)编译自google,谢谢。编者按所有公司都可能与云迁移作斗争,谷歌也不例外。这就是为什么在 Google Cloud 中,我们有一个名为 Alphabet Cloud 的
  • 孟晚舟当选华为轮值董事长,海思何庭波职位曝光

  • 来源:内容来自第一财经,作者:李娜,谢谢。3月28日,第一财经记者获悉,华为投资控股有限公司工会第四届持股员工代表会今日在深圳坂田华为基地完成了最新一届华为投资控股有限公司董
  • 被问懵了:MySQL 自增主键一定是连续的吗?

  • 测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不
  • 光伏上市企业“大变脸”

  • ·会议推荐2023分布式光伏创新峰会(4.25~26 )参观整县分布式光伏项目(江苏连云港)咨询电话:18911205173(微信同号)继跨界潮后,A股市场又迎来了一大波“更名潮”。据统计,自2022年以来