【java】大数据insert的几种技术方案和优缺点

发布于:2025-08-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

在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, col3
    FROM temp_payment_detail
    WHERE 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
  • 理由:兼容多种数据库,灵活性高,适合小批量多次插入


网站公告

今日签到

点亮在社区的每一天
去签到