Mybatis批量处理优化

总结


Mybatis内置的ExecutorType有3种,默认的是simple单句模式,该模式下它为每个语句的执行创建一个新的预处理语句,单句提交sql;batch模式重复使用已经预处理的语句,并且批量执行所有语句,大批量模式下性能更优。

请注意batch模式在Insert操作时事务没有提交之前,是没有办法获取到自增的id,所以请根据业务情况使用。
使用simple模式提交10000条数据,时间为19s,batch模式为6s ,大致情况如此,优化的具体还要看提交的语句情况。
如果需要使用 foreach来优化数据插入的话,需要将每次插入的记录控制在 10-100 左右是比较快的,建议每次100来分割数据,也就是分而治之思想。

普通插入


默认的插入方式是遍历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");


foreach 优化插入


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

<insert id="batchInsert" parameterType="java.util.List">
    insert into table1 (field1, field2) values
    <foreach collection="list" item="t" index="index" separator=","> 
        (#{t.field1}, #{t.field2})
    </foreach>
</insert>



翻译成sql语句也就是

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



foreach 遇到数量大,性能瓶颈
项目实践发现,当表的列数较多(超过20),以及一次性插入的行数较多(上万条)时,插入性能非常差,通常需要20分钟以上

这个时候就需要观察曲线了,10-100个来讲是很快的,当然也要根据项目请来看,总之建议100个就ok了,不要太高。

executeBatch方法


批量执行的一种方式,使用PreparedStatement预编译进行优化。

int insertNum = 100;
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/xxx?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true","root","root123");
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement(
        "insert into table1(field1) values(?)");
for (int i = 0; i < insertNum; i++) {
    ps.setString(1,"大狼狗"+insertNum);
    ps.addBatch();
}
ps.executeBatch();
connection.commit();
connection.close();

开启ExecutorType.BATCH模式
简单的讲就是openSession的时候带上参数ExecutorType.BATCH,可以几乎无损优化你的代码性能。

SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
for (Model model : list) {
    session.insert("insertStatement", model);
}
session.flushStatements();


BatchInsert模式


也是官方针对批量数据插入优化的方法之一

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    TableMapper mapper = session.getMapper(TableMapper.class);
    //自定义你的方法来获取需要插入的数据
    List<TableRecord> records = getRecordsToInsert();
     //BatchInsert
    BatchInsert<TableRecord> batchInsert = insert(records)
            .into(table)
            .map(id).toProperty("id")
            .map(field1).toProperty("field1")
            .map(field2).toProperty("field2")
            .build()
            .render(RenderingStrategy.MYBATIS3);
    batchInsert.insertStatements().stream().forEach(mapper::insert);
    session.commit();
} finally {
    session.close();
}


 

MP 怎么做的?


其实 MyBatis Plus 里边也有一个批量插入的方法 saveBatch,我们来看看它的实现源码:

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
    String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
    return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}


可以看到,这里拿到的 sqlStatement 就是一个 INSERT_ONE ,即一条一条插入。

再来看 executeBatch 方法,如下:

public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
    Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
    return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
        int size = list.size();
        int i = 1;
        for (E element : list) {
            consumer.accept(sqlSession, element);
            if ((i % batchSize == 0) || i == size) {
                sqlSession.flushStatements();
            }
            i++;
        }
    });
}


这里注意 return 中的第三个参数,是一个 lambda 表达式,这也是 MP 中批量插入的核心逻辑,可以看到,MP 先对数据进行分片(默认分片大小是 1000),分片完成之后,也是一条一条的插入。继续查看 executeBatch 方法,就会发现这里的 sqlSession 其实也是一个批处理的 sqlSession,并非普通的 sqlSession。

综上,MP 中的批量插入方案跟我们的批量插入思路其实是一样的。

————————————————
原文链接:

https://blog.csdn.net/moshowgame/article/details/122226553 

10万条数据批量插入,到底怎么做才快?_程序员阿金的博客-CSDN博客_导入10万条数据

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MyBatis 提供了批量更新的功能,可以有效地优化批量更新操作。下面是一些优化批量更新的方法: 1. 使用 BatchExecutor:MyBatis 内部使用 Executor 来执行 SQL 语句,其中 BatchExecutor 是专门用于处理批量操作的实现类。通过配置 `<batch>` 标签,将多个更新操作包装在一个批处理中,可以显著提升性能。 2. 使用批量插入语句:对于大规模的数据插入操作,可以使用批量插入语句来代替单条插入。在 MyBatis 中,可以使用 `<foreach>` 标签来实现批量插入操作,该标签可以将一个集合中的元素逐个插入数据库表。 3. 调整 JDBC 批处理大小:JDBC 本身也支持批量处理操作。通过调整 JDBC 的批处理大小,在一次数据库交互中处理更多的数据,可以减少网络交互的次数,提高性能。可以通过设置 `jdbc.batch.size` 属性或者调用 `Statement` 对象的 `setFetchSize` 方法来更改 JDBC 批处理大小。 4. 使用缓存:MyBatis 提供了一级缓存和二级缓存来减少数据库访问的次数。对于批量更新操作,可以考虑关闭一级缓存(通过设置 `<setting name="localCacheScope" value="STATEMENT"/>`),以避免因为缓存带来的额外开销。 5. 使用数据库的批处理功能:不同的数据库提供了不同的批处理功能,如 MySQL 的 `INSERT INTO ... VALUES (),(),...` 语法,可以一次插入多行数据。你可以根据具体的数据库类型,使用相应的批处理语法,来实现更高效的批量更新操作。 需要注意的是,批量更新操作可能会增加数据库的负载,因此需要根据具体情况进行合理的调优和测试。另外,批量更新操作可能会导致事务回滚时的粒度变大,因此在设计业务逻辑时需要考虑事务的一致性和异常处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值