当前位置: 首页 > news >正文

MyBatis-Plus 批量操作与 rewriteBatchedStatements 优化

目录

① 导读卡片

② 背景与目标

为什么学?

学完能怎样?

③ 核心概念与原理

3.1 saveBatch 的两种来源

3.2 默认行为:循环单条 INSERT

3.3 真正的"一条多值 INSERT"

④ 逻辑图谱与对比

4.1 四种批量插入方案对比

⑤ 核心详解

5.1 方案一:开启 rewriteBatchedStatements(推荐)

5.2 方案二:手写 XML 批量 INSERT(灵活场景)

5.3 方案三:for + 单条 insert(反面教材)

⑥ 典型应用案例

6.1 案例:巡检 7 条明细的批量插入

⑦ 常见坑与最佳实践

7.1 易错点清单

7.2 面试话术

7.3 最佳实践

⑧ 总结与学习路线图

核心要点

自检清单

下一步学习


① 导读卡片

🧩一句话读懂:MyBatis-Plus 的saveBatch默认不是"一条多值 INSERT",而是循环单条 INSERT,开启rewriteBatchedStatements后才能合并为一条 SQL,大幅降低网络往返 🎯适合人群:Java 后端开发者、MyBatis-Plus 使用者、性能优化关注者 📊难度等级:★★★☆☆(中等) ⏱阅读时长:约 10 分钟 💡前置知识:Spring Boot + MyBatis-Plus 基础、JDBC 基本概念


② 背景与目标

为什么学?

大多数人用saveBatch时,以为它是这样执行的:

INSERT INTO table (col1, col2) VALUES (v1, v2), (v3, v4), (v5, v6);

但实际上默认是这样执行的:

INSERT INTO table (col1, col2) VALUES (v1, v2); INSERT INTO table (col1, col2) VALUES (v3, v4); INSERT INTO table (col1, col2) VALUES (v5, v6);

7 条数据就发 7 次网络往返。这个误解可能导致线上性能瓶颈。

学完能怎样?

  • 理解saveBatch底层的真正执行方式

  • 掌握三种批量 INSERT 方案及其性能差异

  • 知道rewriteBatchedStatements的作用和用法

  • 面试时能讲清楚"批量插入怎么优化的"


③ 核心概念与原理

3.1 saveBatch 的两种来源

// 方式一:IService 自带 public interface InspectionDetailService extends IService<InspectionDetail> {} inspectionDetailService.saveBatch(details); ​ // 方式二:ISqlRunner ISqlRunner runner = SqlRunnerFactory.getRunner(detailMapper); runner.saveBatch(details);

两者底层行为完全一致。

3.2 默认行为:循环单条 INSERT

saveBatch的默认实现(简化源码):

public boolean saveBatch(Collection<T> entityList, int batchSize) { int i = 0; String sql = "INSERT INTO table (...) VALUES (?)"; // 单条 INSERT 模板 for (T entity : entityList) { executeBatch(sql, entity); // 每条数据单独发一次 i++; if (i % batchSize == 0) { flushStatements(); // 每 batchSize 条提交一次 } } return true; }
  • 默认batchSize = 1000

  • 虽然叫"批量提交",但 MySQL 层面每批仍然是多条单行 INSERT,只是将多次commit合并为一次

3.3 真正的"一条多值 INSERT"

INSERT INTO table (col1, col2) VALUES (v1, v2), (v3, v4), (v5, v6);

一次网络往返完成所有数据插入。对比上面默认行为:

方式7 条数据的网络往返MySQL 层面
saveBatch默认7 次7 条单行 INSERT
真正的多值 INSERT1 次1 条多值 INSERT

④ 逻辑图谱与对比

4.1 四种批量插入方案对比

批量插入方案 ├─ for + insert() → ❌ 7 次网络往返,最差 ├─ saveBatch() 默认 → 7 次"批量提交"仍为单行 INSERT ├─ saveBatch() + rewriteBatchedStatements → ✅ 1 条多值 INSERT,最推荐 └─ 手写 foreach XML → ✅ 1 条多值 INSERT,最灵活
方式MySQL 层面网络往返代码量推荐程度
for + insert()7 条 INSERT7 次❌ 最差
saveBatch()默认7 条 INSERT(同事务提交)7 次极少⚠️ 一般
saveBatch() + rewriteBatchedStatements1 条多值 INSERT1 次极少(仅加个参数)首选
手写foreach XML1 条多值 INSERT1 次多(需写 XML)✅ 灵活场景

⑤ 核心详解

5.1 方案一:开启 rewriteBatchedStatements(推荐)

原理:MySQL JDBC 驱动提供的参数,开启后会自动将 JDBC batch 提交的多个单行 INSERT 合并为一条多值 INSERT。

配置方式

spring: datasource: druid: url: jdbc:mysql://localhost:3306/mes_db?rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver

效果验证(开启前后对比):

-- 开启前:3 次 INSERT INSERT INTO inspection_detail (...) VALUES (?); INSERT INTO inspection_detail (...) VALUES (?); INSERT INTO inspection_detail (...) VALUES (?); ​ -- 开启后:1 次 INSERT,3 个值 INSERT INTO inspection_detail (...) VALUES (?), (?), (?);

使用代码

@Service @RequiredArgsConstructor public class InspectionSubmitService { private final InspectionDetailService detailService; public void submitInspection() { List<InspectionDetail> details = dto.getDetails(); // 7 条 detailService.saveBatch(details, 100); // batchSize=100 // 开启 rewriteBatchedStatements 后自动合并为一条多值 INSERT } }

5.2 方案二:手写 XML 批量 INSERT(灵活场景)

当需要自定义 SQL 逻辑(如ON DUPLICATE KEY UPDATEINSERT IGNORE)时,手动操作更可控:

<insert id="batchInsert" parameterType="java.util.List"> INSERT INTO inspection_detail (task_id, item_code, number_value, enum_value, photo_url) VALUES <foreach collection="list" item="item" separator=","> (#{item.taskId}, #{item.itemCode}, #{item.numberValue}, #{item.enumValue}, #{item.photoUrl}) </foreach> </insert> @Mapper public interface InspectionDetailMapper { void batchInsert(@Param("list") List<InspectionDetail> details); } // 调用 inspectionDetailMapper.batchInsert(details); // 一条 SQL 完成

5.3 方案三:for + 单条 insert(反面教材)

// ❌ 不要这样做 for (InspectionDetail detail : details) { detailMapper.insert(detail); }

每条数据一次网络往返、一次事务提交。7 条数据 = 7 次数据库交互。


⑥ 典型应用案例

6.1 案例:巡检 7 条明细的批量插入

📋 需求描述:一次巡检提交需插入 7 条巡检明细。要求性能最优,且容易维护。

💻 推荐写法

# application.yml — 仅需加一个参数 spring: datasource: druid: url: jdbc:mysql://localhost:3306/mes_db?rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver @Service @RequiredArgsConstructor public class InspectionSubmitService { private final InspectionDetailService detailService; @Transactional(rollbackFor = Exception.class) public void submitInspection(InspectionSubmitDTO dto) { // ... 校验逻辑 ... // saveBatch + rewriteBatchedStatements = 一条多值 INSERT detailService.saveBatch(dto.toInspectionDetails(), 100); // 更新任务状态 // ... } }

📊 性能对比(300 并发压测):

方案平均响应超时率网络往返
for + insert()3 秒40%7 次
saveBatch()默认2.5 秒30%7 次(同事务提交)
saveBatch() + rewriteBatchedStatements500ms<1%1 次

⑦ 常见坑与最佳实践

7.1 易错点清单

#坑点现象原因✅ 避坑方案
1rewriteBatchedStatements只对 INSERT 有效UPDATE 没合并这是 MySQL JDBC 驱动的设计只关心的 INSERT 场景即可
2一次批量数据量过大报错max_allowed_packet限制单条 SQL 太长超出 MySQL 限制调小 batchSize 或调大max_allowed_packet
3开了参数但没效果抓日志仍看到多行 INSERT使用的不是 JDBCexecuteBatch()saveBatch 底层恰好就是 JDBC batch
4不加参数直接用 saveBatch以为一条 SQL,实际多条对 saveBatch 的误解开 rewriteBatchedStatements 或手写 XML

7.2 面试话术

Q:你们的批量插入怎么做的?

用 MyBatis-Plus 的saveBatch,并在 JDBC URL 上加了rewriteBatchedStatements=true。这样 saveBatch 底层会把多次单行 INSERT 合并为一条多值 INSERT,7 条数据的网络往返从 7 次降到 1 次。这个参数是 MySQL JDBC 驱动提供的,只需要加个连接参数,不用改任何代码。

Q:为什么不直接手写 XML 的 foreach 批量 INSERT?

因为 saveBatch + rewriteBatchedStatements 已经能达到同样的效果,代码量更少、维护更方便。如果以后需要自定义 SQL(比如ON DUPLICATE KEY UPDATE),再考虑手写 XML。目前简单批量插入场景,加个参数就够了。

Q:rewriteBatchedStatements 有什么副作用?

基本没有。它只影响 INSERT 语句的拼装方式,不影响其他操作。唯一要注意的是:如果一次插入几万条数据,可能超过 MySQL 的max_allowed_packet限制,需要调大这个参数。但我们业务场景一次最多 7 条,完全没问题。

Q:saveBatch 的 batchSize 设多少合适?

设 100 比较合理。如果一次插入数据超过 100 条,MyBatis-Plus 会分批执行,每 100 条 flush 一次。我们一次最多 7 条,100 绰绰有余。如果场景是几千上万的批量导入,可以适当调大 batchSize。

7.3 最佳实践

  • 首选方案saveBatch+rewriteBatchedStatements=true,零代码改动,效果好

  • 手写 XML:当需要自定义逻辑(ON DUPLICATE KEY UPDATEINSERT IGNORE、多表关联插入)时使用

  • 控制批量大小:单条 SQL 建议控制在 1000 个以内(不超过 1MB),避免max_allowed_packet限制

  • 避免 for 循环 insert:除非只有 1~2 条数据,否则这是最差的写法


⑧ 总结与学习路线图

核心要点

维度要点一句话记住
默认行为saveBatch 是循环单行 INSERT不是多值 INSERT,是"批量提交"
优化方案rewriteBatchedStatements=true加一个参数,效果天差地别
备选方案手写 foreach XML灵活但有代码量
常见坑误以为 saveBatch 就是多值 INSERT先搞清默认行为再优化

自检清单

  • 我能说清楚 saveBatch 默认是循环单行 INSERT 还是多值 INSERT
  • 我知道 rewriteBatchedStatements 是干什么的、怎么配
  • 我能对比四种批量插入方案的性能差异
  • 我知道什么场景用手写 XML 更合适

下一步学习

阶段一(基础):saveBatch 使用 + rewriteBatchedStatements 配置 → 完成本文 阶段二(进阶):MySQL 执行计划分析、批量插入的 InnoDB 锁机制 阶段三(源码):MyBatis-Plus 批量执行器 SqlBatch 源码、MySQL JDBC Statement.executeBatch() 源码
http://www.jsqmd.com/news/1125922/

相关文章:

  • AutoUnipus:2025终极版U校园智能刷课神器,彻底解放你的学习时间
  • 智能车视觉算法实战:车库场景下的斑马线精准识别与处理策略
  • 如何高效解决中文OCR识别难题:Tesseract tessdata终极优化指南
  • Transformers.js:浏览器端AI应用的范式革命
  • 护理学论文降AI工具免费推荐:2026年护理学毕业论文知网AIGC超标4.8元一次过完整方案
  • Engine-Sim深度解析:实时内燃机模拟与音频合成的工程艺术
  • 全球首例 AI Agent 勒索攻击:自主完成攻击链意味着什么?
  • GPT-5.5与Codex:从对话助手到自主执行智能体的技术演进与应用实践
  • 自己动手开发编译器(七)递归下降的语法分析器
  • 3个核心优势解析:G-Helper如何成为华硕笔记本用户的轻量化性能管理方案
  • 中小企业选 SaaS 定制开发公司,这几个坑我踩过
  • 绝区零一条龙:全自动游戏助手完整指南,解放你的双手!
  • 【OpenHarmony/HarmonyOs 】零敏感权限启动:从 module 配置到 AI 识图禁用的精细化权限方案
  • GBFR-Logs终极指南:从零开始掌握《碧蓝幻想:Relink》伤害统计
  • 企业内网集成Twitter RSS的实战指南:基于办公室的信息流治理
  • 网络日志自动化分析实战:OpenClaw 清洗访问日志、定位异常攻击、生成安全报表
  • 【域攻防】⼯作组内信息收集
  • 数据库设计Step by Step (7)——概念数据建模
  • ICT vs Flying Probe: Which PCB Test Method Actually Reduces Manufacturing Risk?
  • 金蝶AI套件在汽车零部件ERP的5个解法:VMI寄售、滚动计划、批次追溯、ECN管控、模具摊销
  • 2000-2025年全国逐年NDVI栅格数据:基于MODIS MOD13A3的年均值处理方法与数据详解
  • C语言内存管理——内存对齐与共用体union
  • 5分钟掌握ExtDiff:终极免费的Word文档差异比较工具
  • 如何快速配置文件备份工具:ChoEazyCopy 完整教程
  • Win11Debloat终极指南:3分钟让Windows系统性能提升50%的完整教程
  • 鹤壁婚宴宴席,备酒水不浪费又体面
  • 3步掌握高效窗口管理:DockDoor终极工作流优化指南
  • Windows运维体验AMD AI云:领取算力到跑通PyTorch
  • 对象存储的适用场景
  • 公寓管理系统选型趋势:门店经营正在进入总部视角