addbatch() 不能一次性塞几万条再 executebatch(),因内部缓存数组有大小限制(如 mysql 驱动约 1024 条),易触发 oom 或扩容开销;且未启用 rewritebatchedstatements=true 时 batch 无性能收益,需配合事务控制、合理 batch size(500–1000 条)及参数优化。

addBatch() 为什么不能直接塞几万条再 executeBatch()
Java 的 addBatch() 本身不执行 SQL,只是把语句缓存在 PreparedStatement 内部的数组里。但这个数组默认大小有限(不同驱动实现不同,MySQL Connector/J 约为 1024 条),塞太多会触发 OutOfMemoryError 或内部扩容开销剧增;更关键的是,JDBC 驱动往往会在 executeBatch() 时把整个 batch 拼成一条或多条大 SQL 发给数据库,超长 SQL 可能被 MySQL 的 max_allowed_packet 拦截,报错 Packets larger than max_allowed_packet are not allowed。
- 别一次性
addBatch()超过 500–1000 条(MySQL 场景下保守值) - 每次
executeBatch()前检查当前 batch size,到阈值就清空 - 记得调用
clearBatch(),否则下次addBatch()会累加而非覆盖
必须配合 rewriteBatchedStatements=true 才有效
MySQL 默认把 batch 当作多条独立语句串行执行,addBatch() 几乎没收益。只有在 JDBC URL 中显式启用重写机制:rewriteBatchedStatements=true,驱动才会把同结构的 INSERT 合并为 INSERT INTO t VALUES (...), (...), (...) 形式发送——这才是批量插入性能提升的核心。
- URL 示例:
jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true&useServerPrepStmts=false -
useServerPrepStmts=false必须关掉,否则重写失效(服务端预编译不支持 multi-value INSERT) - 该参数对 UPDATE/DELETE 无效,仅对 INSERT 生效
事务控制比 batch size 更影响吞吐量
每执行一次 executeBatch() 就提交一次事务,I/O 和日志刷盘开销会吃掉大部分优化收益。应把多个 batch 包在一个事务里,等累计几千甚至上万条后再 commit()。
- 设置
connection.setAutoCommit(false),手动控制事务边界 - 每 1000–5000 条 executeBatch 后暂不 commit,直到数据写完或达到内存安全阈值
- 出错时用
connection.rollback(),注意 batch 中部分失败时返回的int[]数组含Statement.EXECUTE_FAILED
别忽略 PreparedStatement 的参数绑定开销
高频调用 setString()、setLong() 等方法本身有反射和类型校验成本。如果字段固定且数量不多,可考虑用 setObject(int, Object) 替代具体类型方法;更激进的优化是改用 java.sql.Statement + 字符串拼接(仅限可信数据源,且需严格防 SQL 注入)。
- 避免在循环内反复调用
preparedStatement.setString(i, null),null 值优先用setNull(i, Types.VARCHAR) - 字段类型尽量匹配数据库定义(如 DB 是
TINYINT,别用setInt()强转) - MySQL 8.0+ 驱动中,开启
cachePrepStmts=true&prepStmtCacheSize=250可复用 PS 对象,减少解析开销
rewriteBatchedStatements 与开启后耗时可能相差 5–10 倍;而事务粒度从“每条 commit”调到“每 5000 条 commit”,又可能再降 30%–60%。这些参数组合效果非线性,上线前务必用真实数据集验证。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











