全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 6 分钟 ✍️ juanwangdev

批量操作事务控制

批量操作涉及大量数据写入,事务控制策略直接影响数据一致性和系统稳定性。本文讲解如何在批量操作中平衡事务范围、内存占用与执行性能。

单事务 vs 分块事务

单事务(全量提交)

Java
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User user : allUsers) {
        mapper.insertUser(user);
    }
    session.commit();
} finally {
    session.close();
}
优点缺点
原子性保证,要么全成功要么全失败事务持有锁时间长,影响并发
失败可回滚10 万条数据全部失败才回滚
内存中堆积全部 SQL,可能 OOM
数据库 undo log 膨胀

分块事务(分批提交)

Java
public void batchInsertWithChunks(List<User> users) {
    int chunkSize = 500;
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
        UserMapper mapper = session.getMapper(UserMapper.class);
        for (int i = 0; i < users.size(); i++) {
            mapper.insertUser(users.get(i));
            if ((i + 1) % chunkSize == 0) {
                session.commit();
                session.clearCache();
            }
        }
        session.commit();
    } finally {
        session.close();
    }
}
优点缺点
事务持有锁时间短部分成功部分失败,不具备全量原子性
内存可控失败后已提交的无法回滚
减少 undo log 压力

注意:分块事务牺牲了全量原子性,换取了更好的性能和稳定性。是否接受取决于业务场景。

分块大小选择

分块大小的选择是性能与稳定性的权衡:

分块大小适用场景
100-200每行数据较大(含大字段)、数据库响应慢
500通用推荐值,平衡性能与内存
1000-2000数据行小、数据库性能好、内存充足
5000+极端场景,需充分测试

推荐:从 500 开始,根据实际压测结果调整。

Spring 事务中的批量操作

@Transactional 分块处理

Java
@Service
public class UserBatchService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(rollbackFor = Exception.class)
    public void batchInsert(List<User> users) {
        int chunkSize = 500;
        for (int i = 0; i < users.size(); i++) {
            userMapper.insertUser(users.get(i));
            if ((i + 1) % chunkSize == 0) {
                // Spring 事务会在方法结束时统一提交
                // 如需中途刷新,需使用自定义方式
            }
        }
    }
}

注意:@Transactional 注解会在方法结束时统一提交,中途无法分块提交。如需分块事务,不能依赖 @Transactional,需手动管理 SqlSession。

手动事务分块

Java
public void batchInsertInChunksWithManualTransaction(List<User> users) {
    int chunkSize = 500;
    for (int start = 0; start < users.size(); start += chunkSize) {
        int end = Math.min(start + chunkSize, users.size());
        List<User> chunk = users.subList(start, end);
        processChunk(chunk);
    }
}

private void processChunk(List<User> chunk) {
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
        UserMapper mapper = session.getMapper(UserMapper.class);
        for (User user : chunk) {
            mapper.insertUser(user);
        }
        session.commit();
    } catch (Exception e) {
        session.rollback();
        throw e;
    } finally {
        session.close();
    }
}

异常处理策略

单条失败的处理

Java
public void batchInsertWithErrorHandling(List<User> users) {
    int chunkSize = 500;
    List<User> failedUsers = new ArrayList<>();

    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
        UserMapper mapper = session.getMapper(UserMapper.class);
        for (int i = 0; i < users.size(); i++) {
            try {
                mapper.insertUser(users.get(i));
                if ((i + 1) % chunkSize == 0) {
                    session.commit();
                    session.clearCache();
                }
            } catch (Exception e) {
                failedUsers.add(users.get(i));
                // 记录失败数据,继续处理下一条
            }
        }
        session.commit();
    } finally {
        session.close();
    }

    // 处理失败的数据
    if (!failedUsers.isEmpty()) {
        logFailedUsers(failedUsers);
    }
}

注意:BATCH 模式下,单条 SQL 失败不会立即抛出异常,而是在 commit() 时统一暴露。上述代码适用于 SIMPLE/REUSE 模式。

性能调优建议

1. 关闭不必要的日志

批量操作时,SQL 日志输出会成为性能瓶颈:

XML
<settings>
    <!-- 批量操作期间可降低日志级别 -->
    <setting name="logImpl" value="NO_LOGGING"/>
</settings>

2. 调整数据库参数

数据库参数建议
MySQLinnodb_flush_log_at_trx_commit批量期间设为 2,减少磁盘 IO
MySQLsync_binlog批量期间设为 0,减少 binlog 刷盘
通用适当调大 innodb_log_file_size减少 redo log 切换

3. 使用 rewriteBatchedStatements(MySQL 专用)

text
jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true

开启后,MySQL 驱动会将多条 INSERT 重写为单条多值 INSERT,大幅提升 BATCH 模式性能。

要点总结

  • 全量事务保证原子性但占用资源大,分块事务性能好但不具备全量原子性
  • 分块大小推荐从 500 开始,根据压测调整
  • Spring @Transactional 无法中途分块提交,需手动管理 SqlSession
  • 每批 commit 后调用 clearCache() 清理缓存
  • BATCH 模式下异常在 commit() 时统一暴露
  • MySQL 开启 rewriteBatchedStatements=true 大幅提升 BATCH 性能
  • 批量操作期间可降低日志级别,减少 IO 开销

📝 发现内容有误?点击此处直接编辑

← 上一篇 SQL 语句优化
下一篇 → 结果集大小控制
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库