批量更新是开发中常见的需求,尤其当需要一次性修改大量数据时,合理的批量更新方式能大幅提升性能,减少数据库连接和网络开销。本文用最简单的方式,结合MyBatis和MySQL,介绍几种批量更新的常见写法,优缺点,以及如何解决常见的配置问题,帮助你快速上手。
1. 批量更新的背景和必要性
在实际项目中,如果对一批数据逐条执行更新(如Java中for循环调用单条update),每次都会发起一次数据库请求,网络和磁盘IO开销大,效率低下。批量更新则是一次性将多条更新语句发送给数据库,减少请求次数,提升性能。
2. 常见的批量更新写法及示例
2.1 使用MyBatis的<foreach>拼接多条独立的UPDATE语句(推荐)
这种写法在MyBatis的XML中,利用<foreach>循环拼接多条UPDATE语句,每条语句更新一条记录,语句之间用分号;分隔,最后一次性发送给数据库执行。
示例代码:
<update id="batchUpdateUsers" parameterType="java.util.List">
<foreach collection="list" item="user" separator=";">
UPDATE sys_user
SET password = #{user.password},
update_user_id = #{user.updateUserId},
update_user_name = #{user.updateUserName}
WHERE id = #{user.id}
</foreach>
</update>
Java调用示例:
List<UserEntity> users = ...; // 待更新用户列表
userMapper.batchUpdateUsers(users);
优点:
- 代码简单直观,易于理解和维护。
- 性能较好,因为只需一次数据库请求。
- 适合更新字段较多、数据量适中的场景。
注意事项:
- MySQL默认不支持一次执行多条SQL语句,需要在数据库连接URL中添加参数
allowMultiQueries=true。 - 一次批量更新的条数建议控制在1000条以内,避免SQL过长导致性能问题或异常。
2.2 使用CASE WHEN语句批量更新(不推荐)
这种写法是将多条更新合并成一条UPDATE语句,利用CASE WHEN根据不同id设置不同的字段值。
示例代码:
<update id="batchUpdateUsersCaseWhen" parameterType="java.util.List">
UPDATE sys_user
<set>
password = CASE id
<foreach collection="list" item="user">
WHEN #{user.id} THEN #{user.password}
</foreach>
ELSE password END,
update_user_id = CASE id
<foreach collection="list" item="user">
WHEN #{user.id} THEN #{user.updateUserId}
</foreach>
ELSE update_user_id END,
update_user_name = CASE id
<foreach collection="list" item="user">
WHEN #{user.id} THEN #{user.updateUserName}
</foreach>
ELSE update_user_name END
</set>
WHERE id IN
<foreach collection="list" item="user" open="(" separator="," close=")">
#{user.id}
</foreach>
</update>
缺点:
- SQL语句复杂,代码难维护。
- 当更新字段多、数据量大时,SQL语句会非常长且复杂。
- 数据库执行时需要判断大量
CASE WHEN条件,性能较差。
2.3 使用INSERT ... ON DUPLICATE KEY UPDATE方式
这种方式适合插入或更新操作,先尝试插入数据,如果主键或唯一索引冲突,则执行更新。
示例代码:
<insert id="insertOrUpdateUsers" parameterType="java.util.List">
INSERT INTO sys_user (id, username, password)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.id}, #{user.username}, #{user.password})
</foreach>
ON DUPLICATE KEY UPDATE
password = VALUES(password)
</insert>
要求:
- 需要为业务字段(如
username)创建唯一索引。 - 主键
id不适合自动生成或雪花算法生成的唯一值,需保证业务唯一性。
缺点:
- 容易引发数据库死锁,尤其在高并发场景下。
- 大型公司项目较少采用此方式。
3. 配置注意点:解决“sql injection violation, multi-statement not allow”异常
批量更新时,常见错误是:
sql injection violation, multi-statement not allow
这是因为MySQL默认不允许一次执行多条SQL语句,且数据库连接池(如Druid)中的防火墙过滤器(WallFilter)默认禁止多语句执行。
解决方案:
- MySQL连接串添加参数
在application.yml或application.properties中MySQL连接URL加上:
allowMultiQueries=true
示例:
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
- Druid连接池配置
Druid默认禁止多语句执行,需要在配置中开启:
spring:
datasource:
druid:
filter:
wall:
config:
multi-statement-allow: true
none-base-statement-allow: true
- 使用动态数据源或中间件时
如果使用了如baomidou的动态数据源或shardingsphere分库分表中间件,需要在对应配置中也开启multi-statement-allow,否则仍会报错。
4. 性能对比和批量大小建议
- for循环单条更新:每条数据单独发送SQL,网络和数据库压力大,性能最差。
- foreach拼接多条语句批量更新:一次请求执行多条SQL,性能提升明显,实测100条数据更新时效率比for循环快约80倍。
- case when批量更新:SQL复杂度高,数据库执行压力大,性能一般。
- insert on duplicate key update:性能较好,但易产生死锁,需谨慎使用。
批量大小建议:
- 单次批量更新建议控制在1000条以内,避免SQL过长和数据库压力过大。
- 超过1000条时,分批执行更新。
5. 完整示例代码(MyBatis + Spring Boot)
Mapper接口:
public interface UserMapper {
void batchUpdateUsers(@Param("list") List<UserEntity> users);
}
Mapper XML:
<update id="batchUpdateUsers" parameterType="java.util.List">
<foreach collection="list" item="user" separator=";">
UPDATE sys_user
SET password = #{user.password},
update_user_id = #{user.updateUserId},
update_user_name = #{user.updateUserName}
WHERE id = #{user.id}
</foreach>
</update>
Spring Boot配置示例(application.yml):
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
filter:
wall:
config:
multi-statement-allow: true
none-base-statement-allow: true
6. 总结
- 推荐使用MyBatis的
<foreach>循环拼接多条UPDATE语句,配合MySQL的allowMultiQueries=true和Druid的multi-statement-allow=true配置,实现高效批量更新。 - 避免使用复杂的
CASE WHEN方式,代码难维护且性能一般。 insert ... on duplicate key update适合特定场景,需注意唯一索引和死锁风险。- 控制批量更新的数据量,避免单次更新过大导致性能问题。
- 遇到批量更新报错时,检查数据库连接参数和连接池配置,确保支持多语句执行。
通过以上方法,你可以轻松实现高效、稳定的批量更新操作,提升项目性能和代码质量。
欢迎收藏和分享,助力你在数据库操作中更加游刃有余!