MyBatis基础入门《十二》批量操作优化:高效插入/更新万级数据,告别慢 SQL!

在 《MyBatis基础入门《十一》TypeHandler 详解》 中,我们打通了数据库与 Java 类型的映射通道。

但当面对 导入 10 万条用户数据同步大量订单状态 等场景时,逐条执行 insertupdate 会导致:

  • 数据库连接频繁创建/销毁
  • 事务提交次数过多
  • 网络往返延迟累积

结果:耗时几分钟甚至超时失败!

解决方案 :使用 MyBatis 批量操作(Batch)

本文将手把手教你实现高性能批量写入,并对比多种方案的优劣。


一、为什么普通循环插入这么慢?

复制代码
// ❌ 反面教材:逐条插入(10,000 条 ≈ 10,000 次 SQL + 10,000 次网络交互)
for (User user : userList) {
    userMapper.insert(user); // 每次都是一次独立 SQL
}

性能瓶颈

  • 每次 insert 都是独立事务(自动提交);
  • JDBC 驱动与数据库多次通信;
  • 数据库频繁写 WAL 日志、刷盘。

💡 实测:插入 10,000 条记录,普通方式可能耗时 30s+ ;批量方式可压至 1s 内


二、方案一:SqlSession 的 Batch Executor(推荐)

MyBatis 提供了 ExecutorType.BATCH 模式,底层使用 JDBC 的 addBatch() + executeBatch()

步骤 1:获取 Batch 模式的 SqlSession

复制代码
@Test
public void testBatchInsert() {
    // 1. 获取 BATCH 类型的 SqlSession
    SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    UserMapper mapper = batchSqlSession.getMapper(UserMapper.class);

    try {
        long start = System.currentTimeMillis();
        
        // 2. 循环添加(不立即执行)
        for (int i = 1; i <= 10000; i++) {
            User user = new User();
            user.setUsername("user_" + i);
            user.setProfile(new UserProfile("avatar.jpg", "城市" + i));
            mapper.insert(user); // 仅加入批处理队列
            
            // 3. 每 1000 条 flush 一次,防止内存溢出
            if (i % 1000 == 0) {
                batchSqlSession.flushStatements(); // 提交当前批次
            }
        }
        
        // 4. 提交剩余数据
        batchSqlSession.commit();
        
        long time = System.currentTimeMillis() - start;
        System.out.println("批量插入 10000 条耗时: " + time + " ms");
        
    } catch (Exception e) {
        batchSqlSession.rollback();
        throw e;
    } finally {
        batchSqlSession.close(); // 必须关闭!
    }
}

关键点解析:

  • ExecutorType.BATCH:启用批处理模式;
  • flushStatements():手动触发 executeBatch(),释放内存;
  • commit():最终提交事务;
  • 必须 close():否则资源泄漏!

✅ 优势:

  • 1 次事务提交
  • JDBC 驱动合并 SQL,减少网络往返;
  • 兼容所有数据库(MySQL、Oracle、PostgreSQL 等)。

三、方案二:XML 中使用 <foreach> 构建单条 INSERT(仅限 MySQL)

适用于 一次性插入固定数量数据(如 100~1000 条)。

Mapper XML:

复制代码
<insert id="batchInsertWithForeach">
    INSERT INTO tbl_user (username, profile) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.profile, typeHandler=JsonTypeHandler})
    </foreach>
</insert>

调用:

复制代码
userMapper.batchInsertWithForeach(userList); // 单次 SQL 插入多行

⚠️ 注意:

  • MySQL 默认 max_allowed_packet 限制 SQL 大小(默认 64MB);
  • 超过限制会报错,需分批调用;
  • 不支持 Oracle(语法不兼容)。

✅ 适用场景:中小批量、简单结构、MySQL 环境。


四、方案三:Spring Boot + @Transactional 批量(谨慎使用)

复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void batchInsertInTransaction(List<User> users) {
        for (User user : users) {
            userMapper.insert(user); // 仍在同一事务中
        }
    }
}

❗ 问题:

  • 虽然事务合并了,但 SQL 仍是逐条发送
  • 无 JDBC Batch 优化,性能提升有限;
  • 大数据量易导致 事务日志过大、OOM

不推荐用于万级数据


五、生产环境最佳实践

✅ 1. 分批处理(防 OOM)

  • 单批次建议 500~2000 条(根据字段大小调整);
  • 使用 flushStatements() 主动提交批次。

✅ 2. 关闭自动提交 & 合理设置事务

  • Batch 模式下,整个批次为一个事务
  • 若需部分成功,可在外层控制分段提交。

✅ 3. 数据库调优(MySQL 示例)

复制代码
-- 临时关闭索引更新(插入完成后再重建)
ALTER TABLE tbl_user DISABLE KEYS;

-- 批量插入...

-- 重建索引
ALTER TABLE tbl_user ENABLE KEYS;

或调整参数:

复制代码
# my.cnf
innodb_flush_log_at_trx_commit = 2  # 安全性换性能
bulk_insert_buffer_size = 256M

🔔 生产环境需 DBA 配合评估风险!

✅ 4. 监控与日志

  • 记录每批次耗时、条数;
  • 异常时记录失败数据 ID,支持重试。

六、性能对比实测(10,000 条 User)

方案 耗时 事务数 网络交互 适用场景
普通循环 insert ~32,000 ms 10,000 10,000 次 小数据量
SqlSession BATCH ~800 ms 1 1 次 ✅ 推荐:大数据量
<foreach> 单条 INSERT ~1,200 ms 1 1 次 中小批量、MySQL
Spring @Transactional 循环 ~28,000 ms 1 10,000 次 不推荐

💡 测试环境:MySQL 8.0, HikariCP, 16GB RAM, SSD


七、常见问题解答

❓ Q1:Batch 模式下能获取自增主键吗?

  • 不能!JDBC Batch 不支持返回生成的主键;
  • 解决方案:先批量插入无主键数据,再通过其他字段查询补全(或改用 <foreach>)。

❓ Q2:如何处理部分失败?

  • MyBatis Batch 是"全有或全无";
  • 若需部分成功,需在外层按小批次(如 100 条)循环调用,捕获异常后跳过。

❓ Q3:与 PageHelper、插件冲突吗?

  • 不冲突,但注意插件逻辑不要阻塞 Batch 执行。

八、总结

场景 推荐方案
万级数据导入/同步 SqlSession(BATCH) + 分批 flush
千级以内、MySQL <foreach> 单条 INSERT
需要返回主键 放弃 Batch,用 <foreach> 或分段普通插入
高可靠性要求 小批次 + 事务 + 失败重试机制

核心口诀
"大数据用 BATCH,分批 flush 防 OOM;
小批量用 foreach,主键需求要权衡!"


本文带你掌握 MyBatis 批量操作的性能优化之道,轻松应对海量数据写入挑战。

下一篇我们将深入 MyBatis 与 Lombok、MapStruct 的优雅配合,打造极简 DAO 层!

👍 如果你觉得有帮助,欢迎点赞、收藏、转发!

💬 你在项目中是如何做批量处理的?欢迎评论区分享经验!

相关推荐
何中应5 小时前
【面试题-6】MySQL
数据库·后端·mysql·面试题
路遥_135 小时前
银河麒麟 V10 安装部署瀚高数据库 HighGoDB 4.5 全流程(统信UOS Server 20同理)
数据库
TDengine (老段)5 小时前
从关系型数据库到时序数据库的思维转变
大数据·数据库·mysql·时序数据库·tdengine·涛思数据·非关系型数据库
老兵发新帖5 小时前
ubuntu网络管理功能分析
数据库·ubuntu·php
2301_768350235 小时前
MySQL服务配置与管理
数据库·mysql
+VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue旅游信息推荐系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计·旅游
百锦再5 小时前
国产数据库的平替亮点——关系型数据库架构适配
android·java·前端·数据库·sql·算法·数据库架构
wusp19945 小时前
基于vite + nodejs + MongoDB + vue2 的博客发布系统
数据库·mongodb
全栈小55 小时前
【数据库】浙人医携手金仓数据库,打造全国首个多院区异构多活容灾架构
数据库·1024程序员节·金仓