《MyBatis 批量插入性能优化的 5 个关键参数配置》

MyBatis, 批量插入, 性能优化, MySQL, Spring Boot

前言

这篇文章解决什么问题?就是MyBatis批量插入慢到让人抓狂的问题。适合谁?正在用Spring Boot + MyBatis做数据导入、日志采集、订单同步的Java开发者。你能收获什么?通过压测数据看清batchSize、executorType、rewriteBatchedStatements这三个参数的真实影响,以及另外两个容易被忽略的关键配置,最后给出可直接落地的参数组合建议。

问题背景

上个月有个做电商的老客户找到我,说他们的订单同步接口每次要插入几千条数据,耗时竟然要十几秒。我一看代码,好家伙,循环里单条insert,一条一条地往数据库里塞。这能不慢吗?

其实很多团队都这样,刚开始数据量小没感觉,等业务涨上来,批量插入的性能瓶颈就暴露了。MyBatis的批量插入优化,说白了就是怎么减少数据库交互次数、怎么让MySQL更高效地处理多条插入语句。

原理:三个核心参数到底在干嘛

1. executorType

MyBatis有三种执行器:SIMPLE、REUSE、BATCH。默认是SIMPLE,每次执行SQL都会创建一个新的PreparedStatement。BATCH执行器会复用PreparedStatement,并且攒一批SQL一起发给数据库,减少网络往返和编译开销。

但BATCH执行器有个坑:它不会立即返回自增主键,而且如果SQL写错了,报错信息会延迟到flush的时候才出现。

2. batchSize

这个参数控制每次批量提交的记录数。不是越大越好,因为MySQL的max_allowed_packet限制了单次请求的最大包大小,而且事务太大也会导致锁竞争和回滚成本上升。

3. rewriteBatchedStatements

这是JDBC连接串上的一个参数,默认false。如果为true,MySQL驱动会把多条INSERT语句重写成一条多VALUES的INSERT,比如INSERT INTO t (a,b) VALUES (1,2),(3,4)...。这能大幅减少SQL解析和网络传输的开销。

实操:压测对比

我们用一个Spring Boot项目,MySQL 8.0,JDBC驱动8.0.33,插入10万条数据,每条数据大概100字节。测试环境是4核8G的云服务器。

先看基础配置:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
    username: root
    password: 123456

Mapper接口:

java 复制代码
public interface OrderMapper {
    void batchInsert(@Param("list") List<Order> list);
}

XML:

xml 复制代码
<insert id="batchInsert">
    INSERT INTO t_order (order_no, amount, create_time)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.orderNo}, #{item.amount}, #{item.createTime})
    </foreach>
</insert>

测试代码:

java 复制代码
@SpringBootTest
public class BatchInsertTest {
    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    @Test
    public void testBatchInsert() {
        int total = 100000;
        int batchSize = 1000;
        List<Order> list = new ArrayList<>();
        for (int i = 0; i < total; i++) {
            list.add(new Order("NO" + i, new BigDecimal("100.00"), new Date()));
            if (list.size() == batchSize) {
                try (SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH)) {
                    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
                    mapper.batchInsert(list);
                    sqlSession.commit();
                }
                list.clear();
            }
        }
    }
}

压测结果(单位:秒)

组合 batchSize=500 batchSize=1000 batchSize=5000
SIMPLE + rewrite=false 12.3 11.8 11.5
SIMPLE + rewrite=true 8.1 7.6 7.2
BATCH + rewrite=false 5.4 4.9 4.6
BATCH + rewrite=true 3.2 2.8 2.5

说实话,结果有点出乎意料。BATCH + rewrite=true 在batchSize=5000时最快,2.5秒搞定10万条,比最慢的组合快了近5倍。但batchSize从1000到5000的提升并不大,反而5000时内存占用明显增加。

踩坑记录

坑1:BATCH执行器不返回自增ID

有次我们批量插入后需要拿到自增ID去关联子表,结果发现ID全是null。查了半天才发现是BATCH执行器的问题。解决办法:要么不用BATCH,要么插入后单独查一次。

坑2:rewriteBatchedStatements对老驱动无效

MySQL驱动5.1.x版本对rewriteBatchedStatements支持不完整,升级到8.x才稳定。我们当时线上用的5.1.49,配了参数没效果,白折腾半天。

坑3:batchSize太大导致OOM

有同事把batchSize设成20000,结果插入10万条数据时内存直接爆了。因为MyBatis的foreach会先把所有数据拼成一条SQL,如果list太大,SQL字符串就非常长,内存和网络都扛不住。

另外两个关键参数

除了上面三个,还有两个参数容易被忽略:

4. useServerPrepStmts

这个参数决定是否使用服务端预编译。默认false,但有些连接池配置会把它打开。如果开了服务端预编译,配合rewriteBatchedStatements可能会有问题,因为服务端预编译的语句无法被重写。我们实测发现,useServerPrepStmts=true时,rewriteBatchedStatements失效,性能反而下降。

5. maxAllowedPacket

MySQL服务器端的max_allowed_packet默认是4MB,如果批量插入的SQL超过这个大小,会直接报错。我们当时插入1000条数据,每条1KB,SQL大概1MB,没问题。但如果你插入的是大字段,比如TEXT类型,很容易超限。建议调大到16MB或32MB。

sql 复制代码
SET GLOBAL max_allowed_packet = 16777216;

总结

经过这一轮压测,我们最终采用的配置是:executorType=BATCH,batchSize=1000,rewriteBatchedStatements=true,useServerPrepStmts=false,max_allowed_packet调到16MB。这个组合在10万条数据下耗时2.8秒,比原来的单条插入快了近10倍。

当然,具体数值还得根据你的数据大小和数据库配置来调。建议你拿自己的数据跑一遍压测,别直接照搬。毕竟生产环境千差万别,我们踩过的坑你可能也会遇到。

有问题欢迎在评论区聊聊,一起探讨。

相关推荐
寻求出路的程序媛28 分钟前
分布式 & 高性能 & 高可用 体系、学习重点、面试点
分布式·后端·面试·性能优化
mmsx38 分钟前
osmdroid 手势交互与罗盘方向:缩放联动+传感器融合+定位跟随
android·源码·地图·osmdroid
帅次1 小时前
Google Play 与 Android 17 内存治理:市场格局与趋势分析
android·google play·memory limiter
海兰1 小时前
【 Python 量化交易】第10章:八大经典策略
android·python·kotlin
Rytter2 小时前
Android诈骗裸聊软件逆向分析
android
JMchen1232 小时前
【Android 性能优化实战 60 讲】04 Memory Profiler 高阶玩法:深剖堆内存原理,实战定位 Kotlin 闭包隐式泄漏
android·性能优化·kotlin·实战·源码分析·内存泄漏·memory profiler
Android-Flutter2 小时前
Java线程池 - 内部线程管理机制详解
android
开开心心就好2 小时前
手机悬屏翻译工具外语游戏漫画APP全覆盖
android·前端·javascript·python·游戏·pdf·html
恋猫de小郭2 小时前
AI 时代,也许你的 Flutter 需要一套 Dartastic OpenTelemetry 监控
android·前端·flutter