MyBatis批量插入数据优化

wen java案例 3

本文目录导读:

MyBatis批量插入数据优化

  1. 核心优化方案:MyBatis Batch Executor(推荐)
  2. 极致性能优化:MyBatis-Plus 或 原生Foreach(慎用)
  3. 底层优化手段
  4. 完整优化策略代码示例
  5. 性能对比(100万条数据插入MySQL)
  6. 关键优化总结

针对MyBatis的批量插入优化,核心思路是减少数据库连接交互次数利用数据库原生批量能力以及合理控制事务与内存,以下是经过验证的几种优化方案,按推荐优先级排列:


核心优化方案:MyBatis Batch Executor(推荐)

这是MyBatis内置的批量模式,通过SqlSessionBATCH执行器实现。

代码示例:

// 获取批量模式的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
        mapper.insert(user); // 不会立即执行,而是批量缓存
    }
    sqlSession.commit(); // 一次性批量提交
} finally {
    sqlSession.close();
}

优势:

  • 底层将多次INSERT语句合并为PreparedStatement的批量执行
  • 相比普通循环插入,性能提升5-10倍
  • 不会因单条SQL过长导致数据库报错

注意:

  • 必须使用BATCH模式,默认是SIMPLE(每次执行立即提交)
  • 每500-1000条提交一次,避免事务过大占用内存
  • 不适用于INSERT ... ON DUPLICATE KEY UPDATE等复杂语句(需配合foreach

极致性能优化:MyBatis-Plus 或 原生Foreach(慎用)

单条INSERT拼接成多条:

<insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO user (name, age) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.age})
    </foreach>
</insert>

性能表现:

  • 一次网络往返,数据库解析一次SQL
  • 数据量较小时(<1000条)性能最佳
  • 数据量过大(>5000条)可能导致数据库SQL过长、内存溢出

优化技巧:

// 手动分批处理,防止SQL过长
List<List<User>> partition = Lists.partition(userList, 1000);
for (List<User> batch : partition) {
    mapper.batchInsert(batch); // 每1000条执行一次
}

适用场景:

  • 数据量精确可控(如<2000条)
  • 数据库支持大SQL(MySQL默认max_allowed_packet=4MB,可调大至16MB)

底层优化手段

关闭自动提交

// 必须手动控制事务
sqlSession.commit(); // 替代自动提交
connection.setAutoCommit(false); // 直接操作JDBC时

调整JDBC批量大小

Spring配置:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      # 不推荐直接改批量参数,而是使用MyBatis的BATCH模式

直接操作JDBC:

PreparedStatement ps = conn.prepareStatement(sql);
conn.setAutoCommit(false);
for (User user : users) {
    ps.setString(1, user.getName());
    ps.addBatch();
    if (i % 1000 == 0) {
        ps.executeBatch(); // 每1000条执行一次
        conn.commit();
    }
}
ps.executeBatch();
conn.commit();

使用数据库原生批量特性

MySQL:

-- 关闭唯一索引检查(大数据量插入时临时启用)
SET UNIQUE_CHECKS = 0;
-- 关闭外键检查
SET FOREIGN_KEY_CHECKS = 0;
-- 批量插入后恢复
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;

PostgreSQL:

-- 使用COPY命令(极速)
COPY user (name, age) FROM '/data/users.csv' DELIMITER ',';

完整优化策略代码示例

// 批量插入主方法
public void batchInsertUsers(List<User> userList) {
    // 1. 按批次分割(防止内存溢出)
    List<List<User>> batches = Lists.partition(userList, 500);
    // 2. 使用BATCH模式执行
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        for (List<User> batch : batches) {
            for (User user : batch) {
                mapper.insert(user);
            }
            session.flushStatements(); // 非必要,但可提前释放
        }
        session.commit();
    }
}

性能对比(100万条数据插入MySQL)

方法 耗时(秒) 适用数据量 风险
逐条插入(默认SIMPLE) 120+ 小数据 高事务开销
Batch Executor(500条/批) 3-5 海量数据
Foreach拼接(2000条/批) 2-4 中型数据 SQL过长风险
JDBC原生Batch(1000条/批) 3-5 通用 需手动管理
LOAD DATA INFILE(MySQL) 1-2 超大数据 文件格式限制

关键优化总结

  1. 永远不要用循环单条插入(除非测试数据<100条)
  2. 大数据量优先选择MyBatis的ExecutorType.BATCH
  3. 小批量(<1000条)可用foreach,但需控制批次大小
  4. 始终手动关闭自动提交,在合适时机commit
  5. 监控数据库的max_allowed_packet和连接池配置
  6. 对于极端性能需求,考虑直接使用LOAD DATACOPY命令

最佳实践:

// 通用方案:结合BATCH模式 + 分批次提交
@Transactional
public void insertBatch(List<T> data) {
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    // ... 处理逻辑
}

通过上述方法,MyBatis的批量插入性能可以从每分钟几千条提升到每秒数万条(取决于硬件和数据库配置)。

抱歉,评论功能暂时关闭!