不配 rewritebatchedstatements=true,mybatis 的 批量插入性能最多只有理论值的 1/10;只用 executortype.batch 而不控制分批大小,容易 oom 或触发 mysql 的 max_allowed_packet 限制。

直接上结论:不配 rewriteBatchedStatements=true,MyBatis 的 <foreach></foreach> 批量插入性能最多只有理论值的 1/10;只用 ExecutorType.BATCH 而不控制分批大小,容易 OOM 或触发 MySQL 的 max_allowed_packet 限制。
MyBatis 的 多值插入必须配合 rewriteBatchedStatements=true
很多人以为写个 <foreach></foreach> 就算批量了,其实只是把多条 INSERT 拼成一条 SQL 发过去。但 JDBC 驱动默认仍会把它拆成 N 条单语句执行——每条都走完整解析 + 权限校验 + 日志刷盘流程。
真正起效的前提是数据库连接 URL 显式开启批处理重写:
jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC
这个参数会让 MySQL JDBC 驱动在底层把形如 INSERT INTO t(a,b) VALUES (?,?),(?,?) 的语句,自动转换为更高效的内部批处理调用(类似 addBatch() + executeBatch()),跳过重复解析。
- 不加该参数时,10 万条数据插入耗时约 30 秒(实测)
- 加上后,同样数据降至 3–5 秒(取决于批次大小和硬件)
- MySQL 8.0+ 驱动必须用
mysql-connector-java:8.0.33+,旧版对 rewrite 支持不完整
ExecutorType.BATCH 不等于“自动优化”,要手动 flush + 分批
ExecutorType.BATCH 是 MyBatis 层的缓存机制,它把所有 insert() 调用暂存在内存里,等你调 flushStatements() 才真正发给数据库。但它不会自动分片——如果你传入 10 万条记录,MyBatis 会尝试一次性缓存全部,极易触发 JVM OOM 或 MySQL 报错 Packets larger than max_allowed_packet are not allowed。
正确做法是边插入、边分批刷新:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int batchSize = 1000; for (int i = 0; i
-
batchSize建议设为 500–2000,具体看单条数据体积(如含大文本字段则往小调) - 必须调用
flushStatements(),仅commit()不够——后者只触发事务提交,不强制发送缓冲 SQL -
clearCache()不可省略,否则一级缓存持续增长,GC 压力陡增
MyBatis XML 中 的写法细节决定成败
即使开了 rewriteBatchedStatements=true,XML 里拼 SQL 的方式也影响最终是否能走批处理路径。关键点在于:必须生成单条含多 VALUES 的 INSERT,不能拼出多个 INSERT 语句。
✅ 正确(一条 SQL,多组 VALUES):
<insert id="batchInsert">
INSERT INTO user (username, email) VALUES
<foreach collection="list" item="u" separator=",">
(#{u.username}, #{u.email})
</foreach></insert>
❌ 错误(拼出多条 INSERT,驱动无法合并):
<insert id="badBatchInsert"><foreach collection="list" item="u" separator=";">
INSERT INTO user (username, email) VALUES (#{u.username}, #{u.email})
</foreach></insert>
-
separator=","是核心,确保最终生成的是VALUES (..),(..),(..) - 不要用
separator=";"或空格换行拼多条 INSERT,MySQL 不支持分号分隔的批量语句(除非用存储过程) - 若表有自增主键且需返回 ID,
<foreach></foreach>方式无法获取每条的id,此时只能退回到ExecutorType.BATCH+ 单条插入模式
最容易被忽略的三个硬性依赖
上面所有优化都建立在三个底层条件满足的前提下,缺一不可:
- MySQL 服务端必须用
innodb引擎(MyISAM 不支持事务级批处理) - JDBC 连接池(如 HikariCP)要禁用自动提交:
autoCommit=false,否则每次insert()都隐式commit,彻底废掉批处理 - 事务边界要显式控制:要么用
@Transactional包裹整个方法,要么像上面示例一样手动commit()/rollback(),否则可能卡在未提交状态
这些不是“可选配置”,而是批处理生效的开关。漏掉任意一个,你看到的都是“写了批量代码,跑起来还是逐条慢”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










