【java】大数据insert的几种技术方案和优缺点
文章目录
- 方案零:单条循环插入
- 方案一:ExecutorType.BATCH批量提交(推荐)
- 原理介绍
- 代码实现
- 开启批量会话
- 获取Mapper,循环插入
- Mapper XML 只需单条插入SQL
- 优缺点分析
- 场景建议
- 方案二:分批控制foreach批量插入的SQL长度
- 使用 `<foreach>` 标签拼接多值 SQL
- foreach标签的工作原理
- 性能瓶颈
- SQL字符串过长
- 数据库解析和执行慢
- 批量过大导致JDBC驱动或数据库异常
- 现象总结
- 优缺点分析
- 场景建议
- 方案三:MyBatis-Plus的saveBatch原理与适用场景
- 原理介绍
- 工作机制
- 代码实现
- 直接调用saveBatch
- 核心源码片段(简化说明)
- 优缺点分析
- 类比说明
- 5.5 适用场景
- 不适用场景
- 方案四:insert into ... select ... 语法(最快)
- 原理介绍
- 代码实现
- 直接在 Mapper XML 写 SQL
- Java 端调用
- 也可用于跨表、跨库(需支持)
- 优缺点分析
- 适用场景
- 不适用场景
- 方案五:异步队列/中间件分批入库
- 原理介绍
- 实现方式
- 发送端(生产者)
- 消费端(消费者)
- 典型技术栈
- 伪代码示例
- 优缺点分析
- 适用场景
- 不适用场景
- Java大数据量insert五大技术方案对比与最佳实践
- 方案选择建议
- 1. 业务侧批量写入(10万级以内,需事务)
- 2. 表间大批量迁移/归档/数据清洗
- 3. 高并发写入,需削峰填谷
- 4. 兼容性优先、数据库多样化
在Java项目中,尤其是使用MyBatis作为ORM框架时,批量插入(batch insert)是高频需求。常见的实现方式主要有如下几种:
方案零:单条循环插入
这是最简单粗暴的方法,循环调用单条插入方法:
for (PaymentBatchDetail detail : paymentBatchDetailList) {paymentBatchDetailMapper.insert(detail);
}
这种方式虽然最简单,但每次都需要和数据库建立一次连接、执行一次SQL,效率极低,可以说是反面教材了
方案一:ExecutorType.BATCH批量提交(推荐)
原理介绍
MyBatis 支持三种执行器类型(ExecutorType):
- SIMPLE:默认,每执行一次SQL就提交一次。
- REUSE:复用预编译SQL(较少用)。
- BATCH:批量执行,将多次的SQL操作先缓存起来,最后一次性提交到数据库。
ExecutorType.BATCH 的核心思想:
多条SQL先暂存在内存中,等积累到一定数量后,一次性发送给数据库批量执行。
这种方式避免了超长的拼接SQL,提升了插入效率,也降低了单条SQL的长度风险。
代码实现
开启批量会话
// 注入SqlSessionFactory
@Autowired
private SqlSessionFactory sqlSessionFactory;// 开启BATCH模式的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
获取Mapper,循环插入
DetailMapper mapper = sqlSession.getMapper(DetailMapper.class);
int batchSize = 1000; // 每批提交1000条
List<Detail> list = ...; // 你的数据列表for (int i = 0; i < list.size(); i++) {mapper.insert(list.get(i)); // 这里调用的是单条插入的SQL// 每batchSize条提交一次if ((i + 1) % batchSize == 0 || i == list.size() - 1) {sqlSession.commit();sqlSession.clearCache();}
}
sqlSession.close();
Mapper XML 只需单条插入SQL
<insert id="insert" parameterType="com.tme.it.apply.entity.Detail">INSERT INTO payment_batch_detail (...字段...)VALUES (...#{xxx}...)
</insert>
优缺点分析
优点 | 说明 |
---|---|
避免超长SQL | 每次只执行单条SQL,数据库压力小,稳定性高。 |
提高性能 | 批量提交减少了网络和数据库的交互次数,显著提升插入速度。 |
支持大数据量 | 适合插入上万、甚至几十万条数据。 |
容易回滚 | 某一批次出错,只需回滚该批,不影响已提交的其它批次。 |
缺点 | 说明 |
---|---|
1. 代码稍复杂 | 需要手动管理SqlSession和批量提交逻辑。 |
2. 内存占用 | 大批量时,未提交的数据会占用内存。 |
3. 仅适合写操作 | 不适合复杂的读写混合业务场景。 |
场景建议
- 推荐用于大数据量批量插入,如数据导入、日志归档、历史数据迁移等。
- 批次大小建议根据服务器内存和数据库压力调整(如500~2000条/批)。
- 需要每行进行数据加工的场景
小结
ExecutorType.BATCH 是MyBatis官方推荐的批量插入方式,性能优异,适用范围广,是解决大数据量insert的首选方案。
方案二:分批控制foreach批量插入的SQL长度
使用 <foreach>
标签拼接多值 SQL
这是最直观、最常见的批量插入写法。它通过MyBatis的 <foreach>
标签,将多条数据拼接成一条超长的 INSERT 语句。例如:
Mapper XML 示例
<!-- 批量插入付款批详情 -->
<insert id="insertBatch" parameterType="java.util.List">INSERT INTO detail (id,no,-- ... 省略其它字段 ...platform) VALUES<foreach collection="list" item="item" separator=",">(#{item.id},#{item.no},-- ... 省略其它字段 ...#{item.platform})</foreach>
</insert>
调用方式
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import com.tme.it.apply.mapper.DetailMapper;@Autowired
private DetailMapper detailMapper;// 批量插入
detailMapper.insertBatch(detailList);
分批调用
public void batchInsertByForeach(List<Detail> detailList, int batchSize) {int total = detailList.size();for (int i = 0; i < total; i += batchSize) {int end = Math.min(i + batchSize, total);List<Detail> subList = detailList.subList(i, end);detailMapper.insertBatch(subList);}
}
类比理解
你可以把这种方式理解为:
“把一沓快递单一次性交给快递员,快递员一次性录入所有单子。”
如果单子太多,快递员手忙脚乱,录入时间会变长,甚至可能出错。
foreach标签的工作原理
MyBatis 的 <foreach>
标签会将集合中的每个元素展开,拼接成一条“超长”的 SQL 语句。
比如批量插入1000条数据,最终会生成如下SQL:
INSERT INTO payment_batch_detail (col1, col2, ...)
VALUES(v11, v12, ...),(v21, v22, ...),...(v1000_1, v1000_2, ...);
性能瓶颈
SQL字符串过长
- SQL长度限制
数据库对单条SQL语句的长度有限制(如MySQL默认4MB),超长SQL会报错或被截断。
但是各个数据库的可能不一致,mysql可以通过以下命令查看
SHOW VARIABLES LIKE 'max_allowed_packet';
- 网络传输压力
客户端与数据库之间传输超大SQL,耗时明显增加。 - 内存消耗
MyBatis 需要在内存中拼接和缓存整个大SQL,内存压力增大。
数据库解析和执行慢
- SQL解析耗时
数据库需要解析、优化、执行这条超长的SQL,解析时间随条数线性增长,甚至更快。 - 锁表风险
一次性插入大量数据,容易导致表锁定,影响其它业务操作。 - 回滚压力大
只要有一条数据出错,整个SQL都会回滚,影响批量操作的原子性和健壮性。
批量过大导致JDBC驱动或数据库异常
- JDBC驱动本身对SQL长度和参数个数有限制,超过阈值时会抛出异常。
现象总结
- 数据量小时,foreach批量插入性能还不错。
- 数据量大时,SQL过长,性能急剧下降,甚至报错。
- 这类瓶颈是MyBatis和数据库本身的机制决定的,简单调大批次反而适得其反。
小结
- foreach适合小批量数据插入。
- 大数据量时,必须优化批量插入方式,否则性能和稳定性都无法保证。
优缺点分析
优点 | 说明 |
---|---|
实现简单 | 基于原有的foreach批量插入,只需在Java侧分批调用。 |
避免超长SQL | 每批SQL长度可控,避免超长导致的报错。 |
性能可接受 | 对于中等规模(几千到几万条)数据,性能较好。 |
缺点 | 说明 |
---|---|
还是拼接SQL | 每批次实际上还是一条大SQL,单批过大依然有风险。涉及到mybatis的字段映射 |
批次过小性能下降 | 批次太小则失去批量插入优势,批次太大又有超长风险。 |
事务控制 | 如果每批都新开事务,部分批次成功可能导致数据不一致。需根据业务场景选择是否整体包事务。 |
场景建议
- 适合中等批量插入(如几千~几万条)。
- 批次大小建议结合字段数量、单条数据长度以及数据库SQL长度上限(如MySQL 4MB)来定,一般500~1000条较安全。如果insert单行的字段太多可能需要调整到10-20条
- 对于超大批量(几十万~百万级),建议采用ExecutorType.BATCH或数据库原生导入方案。
小结
分批foreach插入兼顾了实现简易和批量性能,是老项目改造和中等数据量场景的实用方案,但不适合极大数据量。
方案三:MyBatis-Plus的saveBatch原理与适用场景
原理介绍
MyBatis-Plus 是 MyBatis 的增强工具包,极大简化了 CRUD 操作。
其中 saveBatch
方法是其批量插入的常用方案,底层实现其实是分批调用 MyBatis 的批量插入。
工作机制
saveBatch
默认每批插入1000条(可自定义)。- 内部采用
ExecutorType.BATCH
的 SqlSession,循环调用单条插入方法,达到批次后统一提交。 - 支持事务控制,遇到异常可回滚。
本质:
自动帮你分批、批量提交,封装了批处理的繁琐细节,兼顾性能与易用性。
代码实现
直接调用saveBatch
@Autowired
private DetailService detailService;List<PaymentBatchDetail> list = ...; // 待插入的数据// 默认每批1000条
detailService.saveBatch(list);// 或自定义批次大小
detailService.saveBatch(list, 500);
核心源码片段(简化说明)
MyBatis-Plus的saveBatch
大致实现如下:
@Transactional(rollbackFor = Exception.class)
public boolean saveBatch(Collection<T> entityList, int batchSize) {int i = 0;for (T entity : entityList) {insert(entity); // 调用单条插入if (++i % batchSize == 0) {sqlSession.flushStatements(); // 批量提交}}sqlSession.flushStatements(); // 提交剩余的return true;
}
优缺点分析
优点 | 说明 |
---|---|
封装度高 | 一行代码搞定批量插入,无需自己分批、管理事务。 |
性能优秀 | 内部采用批量提交,效率媲美手写BATCH方案。 |
事务友好 | 支持Spring事务,异常自动回滚。 |
代码简洁 | 业务层更专注于数据处理,减少重复代码。 |
缺点 | 说明 |
---|---|
灵活性有限 | 只支持MyBatis-Plus的实体、Service体系,无法直接自定义SQL。 |
批量更新/自定义SQL批处理不适用 | 仅适合简单的批量插入、删除、更新,复杂场景需自定义。 |
依赖MyBatis-Plus | 项目未集成MP时无法使用。 |
伪批量 | 实际还是循环一次次插入的,只是通过批减少了往返次数,实际效果只优于方案零。 |
类比说明
自动驾驶的快递车
- 你只需要告诉快递车要送哪些包裹,车会自动帮你分批、规划路线、安全送达。
全自动厨房
- 你只需把菜单交给厨房,后厨会自动分批烹饪、上菜,无需你操心细节。
5.5 适用场景
- 项目已集成 MyBatis-Plus,且批量插入逻辑简单。
- 需要快速开发、减少重复劳动。
- 批量数据量中等到较大(几千~十几万条)。
- 对事务、异常处理有一定要求。
不适用场景
- 项目未使用MyBatis-Plus。
- 需要复杂的自定义SQL或批量更新/条件插入等复杂操作。
- 超大数据量(百万级以上)时,建议考虑数据库原生导入工具或更底层的批处理。
小结
MyBatis-Plus 的 saveBatch
是批量插入的“开箱即用”方案,极大提升了开发效率,适合大多数中大型数据批量插入场景。性能接近手写BATCH,且代码更优雅。
如需对比三种方案的适用范围和选择建议,请回复 6
,我将为你总结三种方案的对比和最佳实践建议。
非常好,下面详细介绍方案四:insert into ... select ...
语法的原理与局限,包括原理、代码实现、优缺点和适用场景。
方案四:insert into … select … 语法(最快)
原理介绍
insert into ... select ...
是数据库的原生批量插入语法,直接在数据库层面实现数据的批量迁移、插入等操作。
基本语法:
INSERT INTO target_table (col1, col2, ...)
SELECT col1, col2, ...
FROM source_table
WHERE ...;
核心思想:
通过一条 SQL,从源表(或视图、子查询)中查询出数据,直接插入到目标表。
数据的读取和写入都在数据库内部完成,无需Java层循环插入,极大提升效率。
代码实现
直接在 Mapper XML 写 SQL
<insert id="insertFromSelect">INSERT INTO payment_batch_detail (col1, col2, col3)SELECT col1, col2, col3FROM temp_payment_detailWHERE batch_id = #{batchId}
</insert>
Java 端调用
detailMapper.insertFromSelect(batchId);
也可用于跨表、跨库(需支持)
优缺点分析
优点 | 说明 |
---|---|
性能极高 | 纯数据库内部操作,无网络和Java层开销,适合超大批量数据迁移。 |
简洁高效 | 一条SQL即可完成大批量数据插入,开发和维护成本低。 |
支持复杂查询 | 可以配合JOIN、WHERE、函数等,实现复杂的数据转换和插入。 |
缺点 | 说明 |
---|---|
仅适合表间迁移 | 只能将一张表/视图/查询结果的数据插入另一表,不能用于List<实体>等Java对象批量插入。 |
灵活性有限 | 复杂业务逻辑、数据校验、转换难以在SQL层处理。 |
** 事务粒度大** | 一次性插入大量数据,若出错回滚,影响面大。 |
数据库压力大 | 超大数据量时,可能导致锁表、阻塞等问题,需谨慎评估。 |
兼容性问题 | 不同数据库的SQL语法、特性略有差异,跨库迁移需注意。 |
不适合复杂数据加工场景 | 比如ERP、金融存在复杂字段加工的场景。 |
适用场景
- 表间数据迁移、归档、历史表分表等场景。
- 数据同步、数据分批导入(如临时表转正式表)。
- 数据清洗、转换(可配合SQL函数、表达式)。
不适用场景
- 需要从Java内存对象批量插入数据库。
- 需要复杂的业务逻辑、校验、转换。
- 跨库、跨系统的数据迁移(需数据库支持)。
小结
insert into ... select ...
是数据库原生的高效批量插入方案,适用于表间数据迁移、归档和大批量数据处理场景。
但不适合Java对象批量插入和复杂业务逻辑处理,通常作为数据迁移/归档的专项工具,在日常业务批量写入中使用有限。
方案五:异步队列/中间件分批入库
原理介绍
在高并发、大数据量写入场景下,直接同步写库容易造成数据库压力过大、响应延迟、甚至写入失败。
异步队列/中间件分批入库是一种解耦、削峰填谷的常用架构模式:
- 写入请求先进入消息队列(如 Kafka、RabbitMQ、RocketMQ、Redis Stream 等)
- 后端有专门的消费服务,从队列批量拉取消息,分批写入数据库
- 实现业务与数据库写入的解耦,提升系统整体吞吐量和稳定性
实现方式
发送端(生产者)
- 业务系统将待插入的数据(如订单、日志、明细等)异步写入消息队列,快速响应用户请求
消费端(消费者)
- 独立的消费服务不断从队列拉取消息,聚合到一定批量后,采用批量插入方案(如MyBatis BATCH、saveBatch等)写入数据库
- 可根据实际负载动态调整批次大小、消费速率
典型技术栈
- 消息队列:Kafka、RabbitMQ、RocketMQ、Redis Stream、ActiveMQ等
- 消费端:Spring Boot 定时拉取、Spring Cloud Stream、Flink、Spark Streaming等
伪代码示例
// 生产端
orderService.createOrder(orderVo) {// 业务校验// ...// 异步写入队列mqTemplate.send("order-insert-queue", orderVo);
}// 消费端批量入库
@Scheduled(fixedDelay = 1000)
public void batchInsert() {List<Order> batch = mqTemplate.receiveBatch("order-insert-queue", 500);if (!batch.isEmpty()) {orderMapper.batchInsert(batch); // 可用ExecutorType.BATCH或saveBatch}
}
优缺点分析
优点 | 说明 |
---|---|
** 削峰填谷,保护数据库** | 高峰时请求先入队,后台慢慢批量写库,避免数据库被瞬时高并发压垮。 |
** 高可用、解耦** | 业务与入库解耦,数据库故障时可暂存数据,队列可持久化。 |
** 支持横向扩展** | 消费者可多实例并发消费,提升入库能力。 |
** 支持批量优化** | 消费端可灵活采用各种批量插入方案。 |
缺点 | 说明 |
---|---|
** 架构复杂度提升** | 需要引入消息队列、中间件,增加运维和开发成本。 |
** 数据一致性挑战** | 队列与库之间存在延迟,极端情况下可能丢失、重复(需幂等处理)。 |
** 业务实时性降低** | 数据写入库有延迟,适合对一致性要求不高的场景。 |
** 监控与异常处理复杂** | 队列堆积、消费失败等需要完善监控和补偿机制。 |
适用场景
- 高并发写入、瞬时流量高峰(如秒杀、日志收集、物联网数据采集)
- 对写入实时性要求不高,允许秒级/分钟级延迟
- 需要解耦业务与数据库负载,提升系统可用性与弹性
不适用场景
- 强一致性、强实时性业务(如金融核心交易、实时扣款等)
- 数据量很小、并发压力低的场景
小结
异步队列/中间件分批入库是一种应对高并发、大数据量写入的架构型优化,能极大提升系统的吞吐量和稳定性。适用于需要削峰填谷、解耦写入的业务场景,但对实时性和一致性有一定影响,需要配套完善的监控、幂等和补偿机制。
Java大数据量insert五大技术方案对比与最佳实践
方案编号 | 技术方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
1 | MyBatis ExecutorType.BATCH | - 易用,兼容MyBatis生态 - 支持事务、回滚 - 适合中等批量 | - 需手动分批,批量过大易OOM - SQL日志不友好,调试困难 - 适合单库单表 | - 业务侧批量写入 - 数据量10万级以内 - 需要事务一致性 |
2 | foreach分批拼接SQL | - 控制单条SQL长度 - 适合小批量高频次 - 兼容性好 | - SQL长度有限制 - 代码维护复杂 - 批量过大SQL超长 | - 小批量、多次写入 - 需兼容多数据库 |
3 | MyBatis-Plus saveBatch | - 封装好,易用 - 支持分批 - 代码简洁 | - 底层还是分批foreach - 不适合超大批量 - 需引入MyBatis-Plus | - 业务批量入库 - 10万级以内 - 追求开发效率 |
4 | insert into … select … | - 性能极高,数据库原生 - 适合表间迁移、归档 - 支持复杂SQL | - 仅限表间/查询结果 - 业务逻辑有限 - 事务粒度大 | - 表间迁移、归档 - 数据清洗、同步 - 离线批量处理 |
5 | 异步队列/中间件分批入库 | - 削峰填谷,保护数据库 - 高可用、可扩展 - 支持批量优化 | - 架构复杂 - 数据一致性、实时性挑战 - 需幂等、补偿 | - 高并发写入 - 日志、订单、IoT等 - 可容忍延迟业务 |
方案选择建议
1. 业务侧批量写入(10万级以内,需事务)
- 优先推荐:MyBatis ExecutorType.BATCH 或 MyBatis-Plus saveBatch
- 理由:易用,支持事务,适合日常业务批量写入
2. 表间大批量迁移/归档/数据清洗
- 优先推荐:insert into … select …
- 理由:数据库原生,性能极高,适合批量迁移和数据处理
3. 高并发写入,需削峰填谷
- 优先推荐:异步队列/中间件分批入库
- 理由:解耦业务与数据库,提升系统可用性和吞吐量
4. 兼容性优先、数据库多样化
- 优先推荐:foreach分批拼接SQL
- 理由:兼容多种数据库,灵活性高,适合小批量多次插入