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

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

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

相关推荐
martindelophy15 小时前
浏览器视频导出太慢怎么优化?一次 WebCodecs 实践:8 分钟视频耗时降低 31%
性能优化·音视频·优化
geovindu17 小时前
sql:Search-Optimized SQL Queries using mysql 9.0
android·数据库·sql·mysql·数据库开发·数据库架构
CCblandy17 小时前
HTTP 长连接 3 类故障实测:[WinError 10053]、99 个 TIME_WAIT、69 次多余握手,4 种对策怎么选
后端·python·网络协议·http·云原生·性能优化·架构
知之则吱吱18 小时前
Android 开发者验证机制迎来新变化:核心要求、实施时间与开发者应对指南
android·google
墨天梦20 小时前
D09_Retrofit接口与数据转换
android·kotlin·retrofit
电商API_1800790524721 小时前
闲鱼商品列表API接口商品详情API接口请求示例(含一份真实响应数据示例)
android·数据挖掘·网络爬虫·跨境电商·反向海淘
我命由我1234521 小时前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
阿俊-全栈开发21 小时前
LikeShop 单商户标准版商品管理二开:分类、SKU 规格与库存联动实现
android·likeshop·likeshop开源商城
木头科技1 天前
【AI 工程化第十五篇】Spring AI 流式输出高并发性能优化实战:SSE、背压、限流、缓存与灰度发布
人工智能·spring·性能优化