跳转到内容

25. 为什么 MyBatis-Plus 的 saveBatch() 这么慢?


记录一次排查批量插入性能问题的经历,问题根源是一个 JDBC 连接参数。

项目里做批量插入时用的是 MyBatis-Plus 的 saveBatch(),数据量不大,几百条,但执行时间明显偏长。排查后发现问题出在 JDBC 连接配置上,缺少 rewriteBatchedStatements=true

很多人以为用了 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。

在 JDBC 连接 URL 里加上这个参数:rewriteBatchedStatements=true

application.yml
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),
-- ...
;
-- 一次网络往返搞定

这才是真正的批量插入,性能差距可能是数倍到数十倍。

不能和 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=false

理解这个参数为什么有效,需要知道 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,否则批量是假的。