MyBatis Plus批量操作优化指南:从踩坑到实战,百万数据插入效率翻倍!

引言

在实际开发中,我们经常会遇到批量数据写入/更新的场景,比如初始化基础数据、日志批量入库、批量同步第三方数据等。这时候如果直接用MyBatis Plus默认的saveBatchsaveOrUpdateBatch,往往会遇到执行慢、内存溢出、数据库压力大等问题。今天就笔者结合项目实战,分享一套亲测有效的MyBatis Plus批量操作优化方案,帮你把百万数据插入从“分钟级”干到“秒级”!

一、先吐槽:默认批量操作的坑,你踩过几个?

MyBatis Plus的BaseMapper提供了saveBatch(插入)、saveOrUpdateBatch(更新或插入)方法,看似方便,但在大数据量场景下简直是“性能杀手”。我之前踩过的坑主要有3个:

1. 逐条SQL执行,网络IO爆炸

默认情况下(没做任何配置),MyBatis Plus会用foreach拼接成多条单条INSERT语句,比如:

INSERT INTO user (name,age) VALUES ('张三',20);
INSERT INTO user (name,age) VALUES ('李四',22);
...

假设要插入1万条数据,就要和数据库交互1万次!每次交互都要经历TCP连接、数据传输、SQL解析,这时间浪费得让人心疼。

2. 不支持数据库原生批量语法

不同数据库有更高效的批量写入方式:

  • MySQL支持INSERT INTO t (a,b) VALUES (1,2),(3,4)...(多值语法);
  • PostgreSQL支持INSERT ... ON CONFLICT(批量Upsert);
  • Oracle支持MERGE INTO(合并插入)。
    但默认的saveBatch根本没用这些语法,白白浪费了数据库的性能优势。

3. 大数据量直接OOM

如果要插入10万条数据,saveBatch会把所有数据一次性加载到内存,拼接成超长的SQL。这时候要么数据库报SQL长度限制错误,要么JVM内存直接爆掉,尤其在生产环境容易翻车。

二、实战优化方案:从“逐条插”到“批量飙车”

针对上面的痛点,我总结了4个核心优化策略,覆盖插入、更新场景,亲测百万数据量效率提升10倍+!

策略1:用数据库原生批量语法,替代逐条SQL(必做!)

原理:用数据库支持的多值插入语法,把N条INSERT合并成1条SQL,减少网络IO次数。
适用场景:纯插入操作(无重复数据)。

步骤1:自定义Mapper方法

在你的UserMapper接口里新增一个批量插入方法:

public interface UserMapper extends BaseMapper<User> {
    // 批量插入(MySQL多值语法)
    void batchInsert(@Param("list") List<User> list);
}
步骤2:写XML实现批量SQL

UserMapper.xml中,用<foreach>标签拼接多值插入:

<insert id="batchInsert">
    INSERT INTO user (name, age, email)
    VALUES 
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email})
    </foreach>
</insert>
步骤3:调用自定义方法

在Service里直接调用batchInsert,代替原来的saveBatch

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void batchAddUsers(List<User> users) {
        userMapper.batchInsert(users); // 直接调用自定义批量方法
    }
}

效果:实测1万条数据插入,耗时从默认的800ms降到80ms,直接快10倍!

策略2:大数据量分批处理,避免OOM(防崩!)

问题:即使用了多值插入,一次性插10万条数据,数据库可能超时,JVM也可能内存溢出。
解决:把数据按批次拆分,比如每批1000条,循环插入。

手动分批(简单粗暴)

subList切分集合,循环调用批量方法:

public void batchInsertByBatch(List<User> users, int batchSize) {
    int total = users.size();
    for (int i = 0; i < total; i += batchSize) {
        int end = Math.min(i + batchSize, total);
        List<User> subList = users.subList(i, end); // 注意:subList是视图,别修改原集合!
        userMapper.batchInsert(subList);
    }
}
自动分批(MyBatis Plus 3.5.3+)

MyBatis Plus内置了SqlHelper.batch()工具,自动分批处理,更省心:

@Transactional
public void autoBatchInsert(List<User> users) {
    // 每批1000条,自动循环插入
    boolean success = SqlHelper.batch(() -> {
        // 这里传入当前批次的数据(需要自己切分,或者用流式处理)
        List<User> batchList = users.stream().limit(1000).collect(Collectors.toList());
        userMapper.batchInsert(batchList);
    });
    if (!success) {
        throw new RuntimeException("批量插入失败");
    }
}

注意:分批大小建议根据数据库性能调整,MySQL推荐1000-2000条/批,Oracle可能更小(500-1000)。

策略3:Upsert操作用数据库原生语法(避坑!)

痛点saveOrUpdateBatch默认先查是否存在,再决定插入或更新,大数据量时“先查”操作会额外消耗大量SQL查询时间。
优化:用数据库原生的UPSERT语法(如MySQL的ON DUPLICATE KEY UPDATE),一条SQL搞定插入或更新。

步骤1:自定义Upsert方法
public interface UserMapper extends BaseMapper<User> {
    void batchUpsert(@Param("list") List<User> list);
}
步骤2:XML实现批量Upsert(MySQL示例)
<insert id="batchUpsert">
    INSERT INTO user (id, name, age)
    VALUES 
    <foreach collection="list" item="user" separator=",">
        (#{user.id}, #{user.name}, #{user.age})
    </foreach>
    ON DUPLICATE KEY UPDATE 
        name = VALUES(name),  -- 存在则更新name
        age = VALUES(age)     -- 存在则更新age
</insert>

效果:原本需要N次查询+N次插入/更新的1万条数据,现在1条SQL搞定,耗时从500ms降到50ms!

策略4:事务+执行器优化(锦上添花)

事务包裹批量操作

批量操作一定要在事务里执行!否则每条SQL都会自动提交事务,频繁IO更慢。
Spring Boot中直接加@Transactional即可:

@Transactional(rollbackFor = Exception.class)
public void batchInsertWithTx(List<User> users) {
    userMapper.batchInsert(users);
}
配置MyBatis BATCH执行器(适合更新多)

MyBatis的BATCH执行器会把多个SQL缓存在内存,最后一起提交,减少数据库交互。
配置方式(Spring Boot)

@Configuration
public class MyBatisConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        
        // 关键配置:设置批量执行器
        Configuration configuration = new Configuration();
        configuration.setDefaultExecutorType(ExecutorType.BATCH);
        sessionFactory.setConfiguration(configuration);
        
        return sessionFactory.getObject();
    }
}

注意BATCH执行器对插入优化有限(不如多值插入),但批量更新时效果明显!

三、数据库参数调优(加分项)

除了代码层面,数据库本身的参数也会影响批量操作性能。以MySQL为例:

参数推荐值说明
innodb_buffer_pool_size内存的50%-70%增大缓冲池,减少磁盘IO
innodb_flush_log_at_trx_commit2(非生产环境)每秒刷日志,比1(每次提交刷)更快,但可能丢1秒数据
max_allowed_packet1G(根据数据量调整)增大SQL包大小,避免“Packet too large”错误

四、总结:批量操作优化口诀

记住这4点,批量操作性能无忧:

  1. 多值插入:用数据库原生语法,减少网络IO;
  2. 分批处理:大数据量切分,避免OOM和超时;
  3. Upsert原生:替代逐条查询,一条SQL搞定;
  4. 事务+执行器:事务包裹减少提交,BATCH执行器优化更新。

最后提醒:优化前先用EXPLAIN分析SQL执行计划,确认索引是否合理(批量插入时建议暂时禁用非关键索引,插入后重建)。实际效果可能因数据库版本、数据量、硬件配置不同有差异,建议先小批量测试再上线!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码不停蹄的玄黓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值