25. 为什么 MyBatis-Plus 的 saveBatch() 这么慢?
记录一次排查批量插入性能问题的经历,问题根源是一个 JDBC 连接参数。
1. 问题背景
Section titled “1. 问题背景”项目里做批量插入时用的是 MyBatis-Plus 的 saveBatch(),数据量不大,几百条,但执行时间明显偏长。排查后发现问题出在 JDBC 连接配置上,缺少 rewriteBatchedStatements=true。
2. JDBC 批处理的”假批量”
Section titled “2. JDBC 批处理的”假批量””很多人以为用了 saveBatch() 就是批量插入了,其实不然。
MySQL JDBC 驱动有一个默认行为:即使你在代码里调用了批处理 API,它默认仍然逐条发送 SQL 给数据库。
也就是说,没加参数时,底层实际执行的是:
INSERT INTO ticket (col1, col2) VALUES (val1, val2);INSERT INTO ticket (col1, col2) VALUES (val3, val4);INSERT INTO ticket (col1, col2) VALUES (val5, val6);-- ... 每条都是一次单独的网络往返saveBatch() 的 batchSize(默认 1000)控制的只是分批提交的逻辑,并不能让驱动真正合并 SQL。
3. 解决方案
Section titled “3. 解决方案”在 JDBC 连接 URL 里加上这个参数:rewriteBatchedStatements=true
spring: datasource: url: jdbc:mysql://host:3306/db?rewriteBatchedStatements=true&useServerPrepStmts=false加了之后,MySQL JDBC 驱动会自动把多条 INSERT 合并成一条多值 INSERT:
INSERT INTO ticket (col1, col2) VALUES (val1, val2), (val3, val4), (val5, val6), -- ...;-- 一次网络往返搞定这才是真正的批量插入,性能差距可能是数倍到数十倍。
4. 注意事项
Section titled “4. 注意事项”不能和 useServerPrepStmts=true 同时使用!useServerPrepStmts=true 启用服务端预编译,会让 rewriteBatchedStatements 失效。两者同时开启时,批量合并不会生效。
通常的做法:
rewriteBatchedStatements=true&useServerPrepStmts=false完整 URL 示例:
jdbc:mysql://127.0.0.1:3306/your_db ?useUnicode=true &characterEncoding=utf-8 &serverTimezone=Asia/Shanghai &rewriteBatchedStatements=true &useServerPrepStmts=false5. saveBatch()的底层调用链
Section titled “5. saveBatch()的底层调用链”理解这个参数为什么有效,需要知道 saveBatch() 的调用链:
IService.saveBatch() └── SqlSession.flushStatements() └── JDBC executeBatch() └── MySQL JDBC 驱动 ├── rewriteBatchedStatements=false(默认)→ 逐条发送 └── rewriteBatchedStatements=true → 合并为多值 INSERT所以这个参数作用在驱动层,与 MyBatis-Plus 本身无关,换成原生 JDBC 批处理也一样需要加。
| 场景 | 实际行为 |
|---|---|
| 未加参数 | 逐条 INSERT,N 次网络往返 |
加了 rewriteBatchedStatements=true | 合并为单条多值 INSERT,1 次网络往返 |
结论:用 MyBatis-Plus 做批量插入,datasource url 必须加 rewriteBatchedStatements=true,否则批量是假的。