《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倍。

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

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

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
liangshanbo12152 天前
React 性能优化实战
性能优化·react
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas