找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

481

积分

0

好友

64

主题
发表于 21 小时前 | 查看: 6| 回复: 0

在业务系统开发中,批量操作是一个高频且关键的技术场景。

  • 批量导入用户
  • 批量更新订单状态
  • 批量删除无效数据
  • 批量上下架商品

然而,现实项目中超过80%的批量操作实现都存在性能或稳定性问题:

  • for循环中执行单条insert
  • 一次提交十万级数据导致数据库连接池耗尽或事务过大
  • 将批量更新写成N次独立的update语句
  • 使用DELETE ... WHERE id IN (...)处理大量数据导致SQL过长或锁表
  • 操作过大的List直接引发内存溢出(OOM)

本质上,批量操作的核心挑战并非SQL语法,而是工程化设计与性能调优能力。本文将系统性地阐述MyBatis-Plus框架下批量插入、更新、删除的标准化写法,并提供可落地的性能优化方案。

1

批量操作常见问题分析

1. 数据量过大,单次操作压力过高

一次性执行上万条的insertupdatedelete语句,极易导致数据库连接卡顿、事务日志膨胀,甚至触发死锁。

2. 在循环中执行SQL语句

for (...) {
    mapper.insert(item);
}

这是最典型的反模式:性能极差、无法利用数据库批处理能力、可能引发严重的锁竞争。

3. 不了解框架与驱动的批处理机制

许多开发者误以为MyBatis-PlussaveBatchupdateBatchById等方法能处理所有场景,却忽视了其内部实现原理以及更底层的JDBC Batch模式对性能的决定性影响。

2

批量插入:三层级标准方案

批量插入是最易引发性能问题的场景。核心原则是:必须结合分批处理、JDBC批处理模式与合理的事务控制

以下提供三种从通用到高性能的解决方案。

方案一:MyBatis-Plus 原生 saveBatch()(通用推荐)

对于大多数后台管理系统,MyBatis-Plus提供的批插入方法已足够使用:

boolean result = userService.saveBatch(list, 500);

其中,参数500代表批处理大小(batchSize)。

执行流程

  1. 框架将列表按batchSize拆分为多个子集。
  2. 每个子集执行一次INSERT语句(利用foreach生成多值SQL或JDBC批处理,取决于配置)。
  3. 性能可满足日常业务需求。

关键提示:切勿一次性提交数万条数据,将batchSize设置在500~1000是经验证的最佳区间。

适用场景

  • 数据量在5万条以下。
  • 后台管理系统的数据导入功能。
  • 常规的CRUD业务。

局限性

  • 海量数据(如50万以上)迁移或导入。
  • 对性能有极致要求的高频写入任务。
  • 需要基于唯一键进行去重插入的场景。

方案二:Mapper XML + MyBatis foreach(SQL可控)

当需要更精细地控制生成的SQL时,可在XML映射文件中直接编写:

<insert id="batchInsert">
    INSERT INTO user (username, phone)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.phone})
    </foreach>
</insert>

优点

  • SQL语句完全可控,适合复杂字段映射。
  • 将多条数据合并为一条INSERT ... VALUES (...), (...), ...语句,执行效率高于循环单插。
  • 性能通常优于saveBatch()的默认行为。

缺点

  • 当列表过大时,拼接的SQL语句会非常长,可能超出数据库或驱动限制。
  • 需要手动实现分批逻辑(可按foreachcollection大小进行切分)。

方案三:JDBC Batch(极致性能)

面对数十万乃至百万级的数据写入,应直接使用JdbcTemplate或原生JDBC的批处理功能,这是数据库/中间件性能调优的重要手段。

jdbcTemplate.batchUpdate(
    "INSERT INTO user (username, phone) VALUES (?, ?)",
    new BatchPreparedStatementSetter() {
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            User user = list.get(i);
            ps.setString(1, user.getUsername());
            ps.setString(2, user.getPhone());
        }
        public int getBatchSize() {
            return list.size();
        }
    });

原理:JDBC批处理模式只需向数据库发送一次SQL模板,后续仅传输参数数据,极大减少了网络IO与数据库SQL解析开销。

适用场景

  • 历史数据迁移。
  • 从文件(Excel/CSV)导入海量数据。
  • 定时生成的报表数据入库。

3

批量更新

批量更新比插入更容易被误用,典型的低效写法是:

for (User u : list) {
    updateById(u);
}

方案一:MyBatis-Plus updateBatchById()

对于按ID更新多条记录,且更新字段相同的简单场景:

userService.updateBatchById(list, 500);

适用:批量启用/禁用、批量修改状态等。 不适用:每条记录更新的字段不同、WHERE条件复杂、大表高频并发更新。

方案二:批量更新相同字段(高效推荐)

例如“批量上架商品”,只更新status字段:

<update id="batchUpdateStatus">
    UPDATE product
    SET status = #{status}
    WHERE id IN
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</update>

优势:一条SQL完成所有更新,网络交互少,数据库/中间件执行效率高。 场景:批量审批、批量改价(统一价格)、批量标记已读等。

方案三:批量更新不同字段(高级用法)

当每条记录需要更新的字段值都不同时,可以采用foreach生成多条UPDATE语句:

<update id="batchUpdatePrice">
    <foreach collection="list" item="item" separator=";">
        UPDATE product
        SET price = #{item.price}
        WHERE id = #{item.id}
    </foreach>
</update>

关键配置:必须开启数据库连接参数,允许执行多条SQL(以MySQL为例):

jdbc:mysql://...?allowMultiQueries=true

适用:导入Excel后,每条记录有独立的新价格需要更新。 注意:此方式生成的SQL较长,不适合一次更新上万条记录。

4

批量删除

直接使用超长的IN语句进行删除是危险操作:

DELETE FROM user WHERE id IN (1,2,3,...);

风险IN列表超过1000项某些数据库会报错;SQL超长;删除量过大直接锁表。

推荐方案一:分批删除

使用工具类将ID列表分片后,循环调用MyBatis-Plus的删除方法。

Lists.partition(ids, 500).forEach(batch -> {
    userMapper.deleteBatchIds(batch);
});

此方案稳健,易于理解和维护。

推荐方案二:软删除(企业级通用)

绝大多数业务系统采用软删除设计,通过is_deleted字段标记数据状态。

UPDATE user SET is_deleted = 1 WHERE id IN (...);

优点

  • 避免硬删除导致的表锁和性能波动。
  • 数据可恢复,满足审计要求。
  • 对业务连续性影响最小。

推荐方案三:按范围或分页删除(处理海量数据)

对于日志、流水等大数据表,直接按条件范围删除。

-- 按ID范围删除
DELETE FROM log WHERE id BETWEEN 1 AND 50000;
-- 按时间分页删除
DELETE FROM log WHERE create_time < '2022-01-01' LIMIT 10000;

这是清理历史数据的标准做法,能有效控制单次操作的事务大小和锁范围。

5

大批量操作通用模板

一个健壮的批量操作应遵循以下模板,适用于插入、更新、删除及导入场景:

  1. 参数校验:检查输入列表是否为空。
  2. 数据分批:使用固定大小(如500)对原始列表进行分片。
  3. 遍历执行:对每个分片的数据执行具体的批处理SQL。
  4. 事务控制:确保整个批量操作在一个合理的事务内完成,避免单条失败导致部分提交。
  5. 异常处理:捕获批处理异常,记录失败数据,支持重试或人工干预。

此模板可直接作为团队开发规范。

6

批量操作性能优化核心建议

  1. 强制分批:始终设置合理的batchSize(500~1000),这是线上大量实践得出的黄金值。
  2. 禁用自动提交:在批处理执行期间,务必确保处于手动事务控制下,待所有批次成功后再统一提交。
  3. 避免大范围锁:严禁在无WHERE条件或条件过泛的情况下执行更新/删除,如UPDATE table SET status=1
  4. 大表操作使用LIMIT:对于海量数据删除,采用DELETE ... WHERE ... LIMIT n分多次执行,避免长事务和锁表。
  5. 慎用DELETE IN:严格控制IN子句内的元素数量,优先采用分批或范围删除方案。

总结

批量操作的本质是工程化问题,它综合考验开发者对数据库性能、SQL特性、事务机制、框架批处理原理及内存管理的理解。

本文系统介绍了针对MyBatis-PlusJava技术栈的批量操作标准化方案,并提供了从通用到高阶、从插入到删除的完整实践指南与性能调优建议。遵循此体系,无论是处理数据导入、状态同步还是历史清理,都能使你的系统在稳定性、效率与可维护性上达到企业级要求。




上一篇:Parquet文件格式深度解析:为何成为大数据存储与高性能查询的标准选择
下一篇:Java垃圾回收器工作原理与调优指南:从算法解析到生产环境实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-6 23:54 , Processed in 0.068586 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表