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 层!

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

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

相关推荐
科技小花1 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸1 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
凯尔萨厮1 小时前
创建SpringWeb项目(Spring2.0)
spring·mvc·mybatis
D4c-lovetrain1 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希2 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神2 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员2 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java2 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿2 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴2 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存