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

解决mybatis批量更新慢问题

批量更新的实现方式

在数据库上执行批量更新无非2种方式:
其一,在应用层通过循环的方式多次执行单条UPDATE语句,使用该方式性能最差,每次执行UPDATE操作都需要经历"建立连接 -> 执行SQL语句 -> 释放链接"的流程。
其二,在数据库层一次性执行单个或多个SQL语句,该方式将批量操作放在数据库,会导致SQL语句比较大,但是只需要经历一次"建立连接"和"释放连接"的过程,相比第一种方式效率会得到很大改进。

如下通过具体的代码测试进行说明:

  • 表数据总量:50w
  • 批量更新数量:1w

在应用层执行批量更新

特点:一条记录update一次,性能比较差,容易造成阻塞。

通过代码实现逐条更新

for (int i = 0; i < studentList.size(); i++) {Student student = studentList.get(i);sqlSession.update("org.test.mapper.update", student);
}

耗时:437587 ms,大约:7分钟。

通过MyBatis实现逐条更新

使用MyBatis实现循环批量更新,首先得设置MySql支持批量操作,在jdbc链接中需要附加&allowMultiQueries=true属性才行,否则会报错:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;
<!-- 批量更新方式1:逐条更新 -->
<update id="updateStudentBatch1" parameterType="java.util.List"><foreach collection="list" item="item" index="index" open="" close="" separator=";">update student<set>age = #{item.age}</set>where id = #{item.id}</foreach>
</update>

生成的SQL语句是使用;分隔的多条SQL语句:

update student SET age = 0 where id = 0;update student SET age = 1 where id = 1;

如果执行时遇到如下报错:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request

需要将MyBatis的执行超时时间设置大一些,2个地方可以设置:

  1. 在MyBatis全局配置文件mybatis-config.xml中通过参数defaultStatementTimeout指定(单位:秒)
<!-- 设置超时时间,它决定驱动等待数据库响应的秒数。  -->
<setting name="defaultStatementTimeout" value="120"/>
  1. 通过MyBatis中select/update/delete标签的timeout属性指定(单位:秒)
<!-- 批量更新方式1:逐条更新 -->
<update id="updateStudentBatch1" parameterType="java.util.List" timeout="180"><foreach collection="list" item="item" index="index" open="" close="" separator=";">update student<set>age = #{item.age}</set>where id = #{item.id}</foreach>
</update>

在实际测试时发现,即使将time参数往大调整,比如设置为:500,再一次性批量更新10000条记录时依然会报错:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request

但是数据却又被实际更新了!

报错time参数值为500,将单次批量更新数量调整为5000,执行耗时为:378890 ms,大约:6分钟(预估单次批量更新10000条记录耗时约在12分钟左右)。

从测试结果看,通过MyBatis的逐条更新方式比直接在代码层循环更新效率还要低!预估耗时大约相差2倍。

在数据库层执行批量更新

通过MyBatis拼接批量更新SQL,转换为when...then...语法。

更新单个字段

<!-- 批量更新方式:转换为when...then...语法,更新单个字段 -->
<update id="updateStudentBatch2" parameterType="java.util.List">update student<trim prefix="set" suffixOverrides=","><trim prefix="age = case" suffix="end,"><foreach collection="list" item="item" index="index">when id = #{item.id} then #{item.age}</foreach></trim></trim>where id in<foreach collection="list" item="item" index="index" separator="," open="(" close=")">#{item.id}</foreach>
</update>

生成的批量更新SQL如下:

update student
setage = casewhen id = ? then ?when id = ? then ?when id = ? then ?end
whereid in (?, ?, ?)

批量更新10000条记录,耗时:3295 ms,从执行效果看,批量更新的效率大大提升了。

使用该方式批量更新时,务必注意:一定要加where条件,参数中的id为需要更新的数据的id;如果不加where条件,则会全部更新。

还可以对参数进行判断处理,当传递的参数不为空时才执行更新:

<!-- 批量更新方式:转换为when...then...语法,更新单个字段 -->
<update id="updateStudentBatch2" parameterType="java.util.List">update student<trim prefix="set" suffixOverrides=","><trim prefix="age = case" suffix="end,"><foreach collection="list" item="item" index="index"><if test="item.age != null"> <!-- 对参数进行校验判断 -->when id = #{item.id} then #{item.age}</if></foreach></trim></trim>where id in<foreach collection="list" item="item" index="index" separator="," open="(" close=")">#{item.id}</foreach>
</update>

更新多个字段

使用when...then...进行批量更新,当需要更新多个字段时,将如下片段拷贝多次并更改prefixwhen...then...的内容即可。

<trim prefix="age = case" suffix="end,"><foreach collection="list" item="item" index="index"><if test="item.age != null">when id = #{item.id} then #{item.age}</if></foreach>
</trim>
<!-- 批量更新方式3:转换为when...then...语法,更新多个字段 -->
<update id="updateStudentBatch3" parameterType="java.util.List">update student<trim prefix="set" suffixOverrides=","><trim prefix="age = case" suffix="end,"><foreach collection="list" item="item" index="index"><if test="item.age != null">when id = #{item.id} then #{item.age}</if></foreach></trim><trim prefix="name = case" suffix="end,"><foreach collection="list" item="item" index="index"><if test="item.name != null">when id = #{item.id} then #{item.name}</if></foreach></trim></trim>where id in<foreach collection="list" item="item" index="index" separator="," open="(" close=")">#{item.id}</foreach>
</update>

生成的批量更新SQL如下:

update student
setage = casewhen id = ? then ?when id = ? then ?when id = ? then ?end,name = casewhen id = ? then ?when id = ? then ?when id = ? then ?end
whereid in (?, ?, ?)

批量更新10000条记录,耗时:10175 ms 。显然,批量更新耗时跟需要更新的字段数量成正比例关系,需要更新的字段越多,批量更新耗时越大。

【参考】
Mybatis中进行批量更新(updateBatch)
mybatis批量更新及其效率问题

http://www.jsqmd.com/news/59889/

相关文章:

  • qy_蓝桥杯编程系列_编程18 进制转换
  • 详细介绍:kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒
  • anything
  • 递归函数,闭包,装饰器3
  • 从vw/vh到clamp(),前端响应式设计的痛点与进化 - 实践
  • 10413_基于Springboot的智慧养老院管理系统
  • 【Unity URP】Rendering Debugger和可视化MipMap方案
  • How to do a biology experiment for a Physician.
  • 2025–2030 年最紧缺的八大 IC 岗位
  • Firefox 禁用按下 Alt 显示菜单
  • LC 3479(2100) 线段树二分 水果成篮
  • 文件的常用操作
  • 聊聊Oracle数据库的向量能力 - 详解
  • ReAct+LangGraph:构建智能AI Agent的完整指南(建议收藏) - 详解
  • 第七天项目
  • Spring Boot框架中在Controller方法里获取Request和Response对象的2种方式
  • 2025煤炭氟氯测定仪TOP5权威推荐:精准检测选对品牌,奥
  • 2025年上海办公室装修公司口碑排名:迎湖办公室装修实力可靠
  • Scrum 冲刺博客_4
  • 第五天项目
  • [豪の算法奇妙冒险] 代码随想录算法训练营第十四天 | 翻转二叉树、对称二叉树、二叉树的最大深度、二叉树的最小深度
  • 团队作业4——7天敏捷冲刺
  • JAX 训练加速指南:8 个让 TPU 满跑的工程实战习惯
  • 251202 模拟测 总结
  • 【小题狂练A】“一切沉溺者挣扎者向所谓极致献出 最稚嫩的人格”
  • 第三天项目
  • 第7篇Scrum冲刺博客
  • 2025年中国温度传感器主流品牌五大推荐:看哪家品牌适合实验
  • 递归算法设计与实现 - Invinc
  • 第二天项目