bigywater.tech
Spring · 96

批量写入:saveAll、batchUpdate 与 rewriteBatchedStatements

saveAll 为什么这么慢?hibernate.jdbc.batch_size 为什么没效果?batchUpdate 真的是批量吗?rewriteBatchedStatements 到底改变了什么?这篇用服务端的 Com_insert 与 Com_select 计数,把 10 种组合逐个测了一遍。

10000 → 1
加 rewriteBatchedStatements=true 后,batchUpdate 在服务端执行的 INSERT 语句数
366 → 45 ms
JdbcTemplate.batchUpdate 打开 rewriteBatchedStatements 前后的中位耗时
10020 次 select
手动主键但没有实现 Persistable 时,saveAll 额外发出的 select

默认参数

实测 · 本机执行输出
  JdbcTemplate 循环里一行一个 update(同一个事务):中位 460 ms(432~727);服务端 Com_insert +10000,Com_select +0;表里 10000 行
  JdbcTemplate.batchUpdate(sql, List<Object[]>)(同一个事务):中位 366 ms(351~435);服务端 Com_insert +10000,Com_select +1;表里 10000 行
  JPA saveAll,主键 IDENTITY(已设 hibernate.jdbc.batch_size=500):中位 602 ms(558~816);服务端 Com_insert +10000,Com_select +0;表里 10000 行
  JPA saveAll,手动指定主键 + Persistable.isNew()=true(batch_size=500):中位 518 ms(418~628);服务端 Com_insert +10000,Com_select +20;表里 10000 行
  JPA saveAll,手动指定主键但不实现 Persistable(batch_size=500):中位 1008 ms(913~1191);服务端 Com_insert +10000,Com_select +10020;表里 10000 行

五种写法服务端都是 10000 条 INSERT;IDENTITY 让 batch_size 失效;手动主键不实现 Persistable 会多出 10020 次 select。

rewriteBatchedStatements

实测 · 本机执行输出
  JdbcTemplate 循环里一行一个 update(同一个事务):中位 441 ms(412~593);服务端 Com_insert +10000,Com_select +0;表里 10000 行
  JdbcTemplate.batchUpdate(sql, List<Object[]>)(同一个事务):中位 45 ms(45~54);服务端 Com_insert +1,Com_select +1;表里 10000 行
  JPA saveAll,主键 IDENTITY(已设 hibernate.jdbc.batch_size=500):中位 583 ms(530~754);服务端 Com_insert +10000,Com_select +0;表里 10000 行
  JPA saveAll,手动指定主键 + Persistable.isNew()=true(batch_size=500):中位 94 ms(90~127);服务端 Com_insert +20,Com_select +20;表里 10000 行
  JPA saveAll,手动指定主键但不实现 Persistable(batch_size=500):中位 653 ms(648~876);服务端 Com_insert +20,Com_select +10020;表里 10000 行

batchUpdate 合并成 1 条多行 insert;saveAll(手动主键 + Persistable)合并成 20 条;IDENTITY 不受影响。

分步演示

批量写入
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Hibernate 7、Spring Data JPA 4;MySQL 是本机自己起的 26.7.0 实例,mysql-connector-j 默认参数,另一次运行加了 rewriteBatchedStatements=true)。插入 10000 行,每种写法 3 次取中位数;JdbcTemplate 与 JPA 都放在同一个事务里(第一版把 JdbcTemplate 放在自动提交下,多了 10000 次提交,不公平,已改);hibernate.jdbc.batch_size=500、order_inserts=true;服务端 Com_insert / Com_select 取自 SHOW GLOBAL STATUS 的差值。Com_select 在 isNew 为 true 的写法里有 +20,我没有追究是什么 select
    • 没有验证:PostgreSQL 的 reWriteBatchedInserts;JPA 用 TABLE 或自定义序列生成器时的批量(MySQL 没有原生序列);StatelessSession 与 JDBC 直接批量;超大批量的内存与 max_allowed_packet 限制;批量插入对主键索引与二级索引的影响;失败时部分提交的行为;rewrite 对 update、delete 的作用
    京ICP备2026067502号-1京公网安备11010502063487号