mybatis批量更新不支持高并发,batchexecutor本质是单线程串行批处理,通过addbatch()+executebatch()减少网络往返;提升吞吐需靠合理批大小(500–2000)、分片处理、连接池调优及jdbc参数优化,而非并发调用。

MyBatis 本身不直接支持高并发批量更新,BatchExecutor 的设计目标是单线程批处理,而非并发压测场景。它通过 JDBC 的 addBatch() + executeBatch() 减少网络往返,但本质仍是串行累积+一次性提交,无法“并发执行”批操作。想压榨批处理网络性能,关键不在并发调用 BatchExecutor,而在合理组织批大小、事务粒度与连接资源。
BatchExecutor 的真实作用:减少 round-trip,不是提升吞吐并发
BatchExecutor 把多条 SQL 缓存在内存中,等达到 batchSize(默认 1000)或显式调用 flushStatements() 时,才通过一次 executeBatch() 发送给数据库。这省去了每条语句单独网络交互的开销,但整个过程是单 Connection、单 Statement、单线程执行的。多个线程各自持有一个 SqlSession(配 BatchExecutor),相当于多个独立批处理通道——这是“并行批处理”,不是“并发执行同一批”。
- 每个 SqlSession 绑定独立 Connection 和 PreparedStatement,互不干扰
- 批大小建议设为 500–2000,太小起不到聚合效果,太大易 OOM 或触发数据库单次批上限(如 MySQL max_allowed_packet)
- 务必手动调用
sqlSession.flushStatements()触发实际执行,否则可能一直缓存不发
真正提升高并发批量更新吞吐的关键配置
网络性能瓶颈常出现在连接争抢、事务锁等待、JDBC 驱动参数不合理。BatchExecutor 只是其中一环:
-
连接池必须支持足够并发连接:HikariCP 设置
maximumPoolSize≥ 并发批任务数,避免线程阻塞在 getConnection() -
关闭自动提交,手动控制事务边界:每个批对应一个事务(
sqlSession.commit()),避免每条 SQL 自动 commit 带来的日志刷盘开销 -
JDBC URL 加上批处理优化参数:MySQL 示例:
?rewriteBatchedStatements=true&useServerPrepStmts=false&cachePrepStmts=true,让驱动把多条 INSERT 合成一条多值语句发送 - 慎用 SELECT FOR UPDATE 或复杂 WHERE 条件:批量 UPDATE 若带子查询或非索引条件,容易引发行锁升级或全表扫描,拖慢整体吞吐
超大批量更新的实用分片策略
单次提交几万甚至百万级记录?别硬扛。应主动拆分,结合业务逻辑做可控分片:
- 按主键/时间范围切分数据(如 id % 100),每个分片由独立线程+独立 SqlSession 处理
- 使用
ExecutorType.BATCH创建 SqlSession,每个线程维护自己的批缓冲 - 每个分片内再设二级批大小(如每 1000 条 flush 一次),防止单个分片内存溢出
- 失败时可精确重试某一分片,而非全量回滚
替代方案:比 BatchExecutor 更适合高吞吐的路径
当数据量达百万级以上且对延迟敏感时,原生 MyBatis 批处理已非最优:
- 直连 JDBC + rewriteBatchedStatements:绕过 MyBatis 框架开销,手写 PreparedStatement.addBatch(),性能更可控
-
数据库原生批量导入工具:MySQL 的
LOAD DATA INFILE、PostgreSQL 的COPY,速度比 JDBC 批处理快 5–10 倍 - 异步消息 + 消费端合并写入:用 Kafka/RocketMQ 聚合变更事件,消费端按 key 分组+定时批量落库,削峰填谷
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











