mybatis plus saveBatch 速度慢,批处理 rewriteBatchedStatements=true

探讨MySQL JDBC驱动rewriteBatchedStatements参数的作用,实验表明其能显著提升INSERT、UPDATE及DELETE操作性能,通过批量执行SQL语句实现效率优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MySQL Jdbc驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,直接造成较低的性能。

 

只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL (jdbc:mysql://ip:port/db?rewriteBatchedStatements=true)。不过,驱动具体是怎么样批量执行的? 你是不是需要看一下内幕,才敢放心地使用这个选项? 下文会给出答案。

 

另外,有人说rewriteBatchedStatements只对INSERT有效,有人说它对UPDATE/DELETE也有效。为此我做了一些实验(详见下文),结论是: 这个选项对INSERT/UPDATE/DELETE都有效,只不过对INSERT它为会预先重排一下SQL语句

 

注:本文使用的mysql驱动版本是5.1.12

 

实验记录:未打开rewriteBatchedStatements时

未打开rewriteBatchedStatements时,根据wireshark嗅探出的mysql报文可以看出,

batchDelete(10条记录)  =>  发送10次delete 请求

batchUpdate(10条记录)  =>  发送10次update 请求

batchInsert(10条记录)  =>  发送10次insert 请求

 

也就是说,batchXXX()的确不起作用

 

实验记录:打开了rewriteBatchedStatements后

打开rewriteBatchedStatements后,根据wireshark嗅探出的mysql报文可以看出

 

batchDelete(10条记录)  =>  发送一次请求,内容为”delete from t where id = 1; delete from t where id = 2; delete from t where id = 3; ….”

batchUpdate(10条记录)  =>  发送一次请求,内容为”update t set … where id = 1; update t set … where id = 2; update t set … where id = 3 …”

batchInsert(10条记录)  =>   发送一次请求,内容为”insert into t (…) values (…) , (…), (…)”

 

对delete和update,驱动所做的事就是把多条sql语句累积起来再一次性发出去;而对于insert,驱动则会把多条sql语句重写成一条风格很酷的sql语句,然后再发出去。 官方文档说,这种insert写法可以提高性能(”This is considerably faster (many times faster in some cases) than using separate single-row INSERT statements”)

 

一个注意事项

需要注意的是,即使rewriteBatchedStatements=true, batchDelete()和batchUpdate()也不一定会走批量: 当batchSize <= 3时,驱动会宁愿一条一条地执行SQL。所以,如果你想验证rewriteBatchedStatements在你的系统里是否已经生效,记得要使用较大的batch.

 

 

对应的代码

最后可以看下对应的MySQL  JDBC驱动的代码,以加深印象:

 

clip_image002[4]

 

<think>好的,用户之前询问了如何在使用MyBatis Plus时进行批量插入并关联主表和流程表。现在用户进一步说明他们使用的是MyBatis Plus,需要更具体的指导。我需要回顾之前的回答,看看是否有需要调整的地方,以确保适用于MyBatis Plus。 首先,之前的回答提到了JPA和JDBC两种方案,但用户现在用的是MyBatis Plus,所以需要调整示例代码和策略。MyBatis Plus有自己的批量插入方法,比如使用ServiceImpl的saveBatch方法,但需要注意其默认的批量操作并不是真正的批量SQL,而是分批次插入,需要配置正确的批量执行器。 接下来,主表插入后获取主键的方式不同。MyBatis Plus在插入后会自动回填主键,前提是实体类的主键字段配置了@TableId(type = IdType.AUTO)或者其他生成策略。需要确认用户是否正确配置了主键生成策略,以确保流程表能正确获取到主表的主键ID。 然后,关联关系的建立。在MyBatis Plus中,可能需要手动设置外键值,而不是像JPA那样通过对象关联自动处理。需要指导用户如何在插入主表后,遍历主表记录,获取生成的主键,然后为每个流程表记录设置对应的order_id。 性能优化方面,MyBatis PlussaveBatch方法默认可能不会启用真正的批量插入,需要用户配置SQL注入器,使用ExecuteBatch类型的执行器。同时,可以建议使用事务管理,批量提交,以及结合数据库连接池参数优化,比如rewriteBatchedStatements等。 验证部分,需要检查主键是否回填,流程表的外键是否不为空。此外,数据库外键约束的开启和测试也是必要的。 可能用户在实际操作中遇到的问题包括主键未正确生成、批量插入效率低、关联关系未正确设置等。需要针对MyBatis Plus的特性给出解决方案,比如正确的主键策略配置、启用真正的批量插入、手动设置外键值等。 总结,我需要将之前的JPA示例转换为MyBatis Plus的代码示例,突出MyBatis Plus的特性和最佳实践,确保用户能够顺利实现批量插入并正确关联主表和流程表。</think>以下是基于**MyBatis Plus**实现主表与流程表批量插入关联的完整方案,涵盖$\textcolor{green}{关键配置}$、$\textcolor{green}{关联策略}$和$\textcolor{green}{性能优化}$: --- ### $\textcolor{green}{一、基础配置}$ #### $\textcolor{orange}{1.1 实体类定义}$ ```java // 主表实体(Order) @TableName("t_order") public class Order { @TableId(type = IdType.AUTO) // 自增主键 private Long orderId; private String orderNo; // 其他字段... } // 流程表实体(OrderProcess) @TableName("t_order_process") public class OrderProcess { @TableId(type = IdType.AUTO) private Long processId; private Long orderId; // 外键字段 private String processType; // 其他字段... } ``` #### $\textcolor{orange}{1.2 Mapper接口增强}$ ```java public interface OrderMapper extends BaseMapper<Order> { // 批量插入方法(需在XML中实现) int insertBatch(@Param("list") List<Order> list); } public interface OrderProcessMapper extends BaseMapper<OrderProcess> { @Insert("<script>" + "INSERT INTO t_order_process(order_id, process_type) VALUES " + "<foreach collection='list' item='item' separator=','>(#{item.orderId}, #{item.processType})</foreach>" + "</script>") int batchInsert(@Param("list") List<OrderProcess> list); } ``` --- ### $\textcolor{green}{二、关联插入实现}$ #### $\textcolor{orange}{2.1 分阶段插入法(推荐)}$ ```java // 第一阶段:插入主表 List<Order> orders = dataList.stream() .map(d -> new Order(d.getOrderNo())) .collect(Collectors.toList()); // 使用MyBatis Plus批量插入(需开启批量模式) boolean mainResult = orderService.saveBatch(orders); if(!mainResult) throw new RuntimeException("主表插入失败"); // 第二阶段:构建流程数据 List<OrderProcess> processes = new ArrayList<>(); for (Order order : orders) { processes.add(new OrderProcess(order.getOrderId(), "PAYMENT")); processes.add(new OrderProcess(order.getOrderId(), "APPROVAL")); } // 批量插入流程表 boolean processResult = orderProcessService.saveBatch(processes); ``` #### $\textcolor{orange}{2.2 SQL执行验证}$ ```sql -- 检查主表记录数 SELECT COUNT(*) FROM t_order WHERE order_no LIKE 'TEST%'; -- 检查关联完整性 SELECT * FROM t_order_process WHERE order_id IN ( SELECT order_id FROM t_order WHERE order_no LIKE 'TEST%' ); ``` --- ### $\textcolor{green}{三、性能优化方案}$ #### $\textcolor{orange}{3.1 启用真批量模式}$ ```yaml # application.yml配置 mybatis-plus: global-config: db-config: id-type: auto configuration: default-executor-type: batch # 启用批处理执行器 ``` #### $\textcolor{orange}{3.2 事务优化}$ ```java @Transactional(rollbackFor = Exception.class) public void batchInsertWithTransaction(List<Order> orders, List<OrderProcess> processes) { orderService.saveBatch(orders); orderProcessService.saveBatch(processes); } ``` #### $\textcolor{orange}{3.3 分批处理(10万+数据量)}$ ```java // 主表分页插入 int batchSize = 2000; ListUtils.partition(orders, batchSize).forEach(batch -> { orderMapper.insertBatch(batch); // 使用自定义批量方法 }); // 流程表分页插入 ListUtils.partition(processes, batchSize*2).forEach(batch -> { orderProcessMapper.batchInsert(batch); }); ``` --- ### $\textcolor{green}{四、常见问题解决}$ #### $\textcolor{orange}{4.1 主键未回填问题}$ $\textcolor{red}{症状:}$流程表的order_id字段为null $\textcolor{blue}{解决方案:}$ 1. 确认主表使用`@TableId(type = IdType.AUTO)` 2. 检查数据库自增字段设置 3. 在插入后立即调用`order.getId()`触发回填 #### $\textcolor{orange}{4.2 批量插入效率低}$ $\textcolor{red}{优化步骤:}$ 1. 在JDBC URL添加参数: ```properties url: jdbc:mysql://host/db?rewriteBatchedStatements=true&useSSL=false ``` 2. 调整Service层批量提交大小: ```java orderService.saveBatch(orders, 1000); // 每批1000条 ``` #### $\textcolor{orange}{4.3 多线程冲突}$ ```java // 使用并行流时的线程安全写法 List<OrderProcess> processes = orders.parallelStream() .flatMap(order -> Stream.of( new OrderProcess(order.getOrderId(), "TYPE1"), new OrderProcess(order.getOrderId(), "TYPE2") )) .collect(Collectors.toList()); ``` --- ### $\textcolor{green}{五、方案对比}$ | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|---------| | saveBatch() | 使用简单 | 受限于框架批量逻辑 | 小数据量(≤1万) | | 自定义XML | 完全控制SQL | 需要手写SQL | 复杂批量插入 | | 存储过程 | 极高性能 | 维护成本高 | 超大数据量(≥50万) | --- $\textcolor{red}{关键总结:}$ 1. **关联绑定**:通过内存中主表对象获取ID后显式设置到流程表 2. **事务控制**:建议将主表和流程表插入放在同一事务中 3. **性能平衡点**:常规项目建议每批2000-5000条数据提交一次
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值