mysql 报错 Batch update returned unexpected row count from update [0]

本文介绍了一种常见的数据库操作错误:批量更新时返回意外的行数。此问题通常发生在根据条件更新记录时,若条件不精确导致匹配多条记录。文章提供了排查及修复方法,建议精确匹配目标记录或清理冗余数据。

今天在跟进站点访问量代码的时候,出现了如图的错误:



百度之后发现该错误的原因是因为在执行saveOrUpdate修改数据库数据时,根据条件检索到多条数据,如图所示,

UPDATE 表 SET 字段 = val WHERE 条件 ,根据where条件能检索到两条以上的数据,那么执行update便会报错 

            Batch update returned unexpected row count from update [0];

删除多余的数据就可以解决了 。


你遇到的异常信息如下: ``` Batch update returned unexpected row count from update [0]; actual row count: 2; expected: 1; nested exception is org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 2; expected: 1 ``` --- ## ✅ 错误解释 这个异常是 **Hibernate 特有的乐观锁检查机制** 触发的,通常出现在使用了 `@Version` 字段(乐观锁)的 JPA 实体更新时。 ### 🔍 异常含义拆解: | 部分 | 含义 | |------|------| | `Batch update returned unexpected row count` | 批量更新返回了意外的影响行数 | | `actual row count: 2` | 数据库实际影响了 **2 行** | | `expected: 1` | Hibernate 期望只影响 **1 行** | | → 结果 | 抛出 `BatchedTooManyRowsAffectedException` | 👉 虽然数据确实被更新了(2行),但 Hibernate 认为“不该发生”,于是报错中断。 --- ## 🧨 常见触发场景 ### ❌ 场景 1:**实体未定义主键或主键不唯一,导致 update 语句命中多行** 例如: - 数据库中存在两条 `(id=1)` 的记录(数据重复) - 或你在 save 时传入的对象没有正确设置 ID - 导致生成的 SQL 类似于: ```sql UPDATE table SET name = ?, version = ? WHERE id = 1 AND version = 0 ``` 如果有 **两条数据的 id=1 且 version=0**,就会更新 2 行 → 触发异常 --- ### ❌ 场景 2:**使用了 `@Version` 乐观锁,但业务代码并发修改同一数据** 比如两个线程同时加载同一个实体: | 时间 | 线程 A | 线程 B | |------|--------|--------| | t1 | load entity (version=0) | load entity (version=0) | | t2 | update → SET version=1 WHERE version=0 | update → SET version=1 WHERE version=0 | | t3 | 成功,影响 1 行 | 也执行,影响 1 行? | 但如果数据库中此时已有其他方式修改过这条数据(如手动改库),可能导致条件匹配到多个旧版本。 --- ### ❌ 场景 3:**自定义 JPQL 更新语句绕过了 Hibernate 的版本控制逻辑** 例如你写了这样的方法: ```java @Modifying @Query("UPDATE MyEntity e SET e.status = :status WHERE e.date = :date") int updateByDate(@Param("status") String status, @Param("date") String date); ``` ⚠️ 这个更新会直接执行 SQL,不会自动处理 `@Version` 字段! → 当你后续再用 JPA 加载并更新该实体时,Hibernate 发现:“我预期 version=0 的只有一条,结果 update 时却有两条符合”,于是抛异常。 --- ## ✅ 根本原因总结 > **Hibernate 使用 `@Version` 字段进行乐观锁控制,在执行 update 时预期只会修改 1 条记录。** > > 但数据库返回说“我改了 2 条”,这与它的预期不符 → 抛出异常终止操作。 这其实是 Hibernate 在帮你 **防止数据覆盖错误**,是一种保护机制。 --- ## ✅ 解决方案 ### ✅ 方案 1:确保数据库主键唯一 + 数据无重复(首要) 运行 SQL 检查是否存在重复主键: ```sql SELECT id, COUNT(*) FROM your_table_name GROUP BY id HAVING COUNT(*) > 1; ``` 如果有重复数据,请清理: ```sql -- 示例:保留 version 最大的那条,删除其他的 DELETE t1 FROM your_table t1 INNER JOIN your_table t2 WHERE t1.id = t2.id AND t1.version < t2.version; ``` --- ### ✅ 方案 2:避免使用非主键字段做批量更新(尤其是带 `@Version` 的实体) 如果你一定要批量更新,不要用原生 JPQL 直接更新带 `@Version` 的实体。 ✅ 正确做法: ```java List<MyEntity> entities = repository.findByDate(date); for (MyEntity e : entities) { e.setStatus("DONE"); repository.save(e); // 自动递增 version,安全 } ``` 或者使用原生 SQL(绕过 Hibernate 控制)—— 但你要自己负责版本一致性: ```java @Modifying @Query(value = "UPDATE your_table SET status = ?1, version = version + 1 WHERE date = ?2", nativeQuery = true) void updateWithVersionIncrement(String status, String date); ``` > ⚠️ 注意:这种方式不会触发 Hibernate 的乐观锁检测,需谨慎使用。 --- ### ✅ 方案 3:关闭批处理中的行数校验(不推荐,仅应急) 在 `application.yml` 中添加: ```yaml spring: jpa: properties: hibernate.jdbc.batch_versioned_data: false ``` 或等价写法: ```properties spring.jpa.properties.hibernate.jdbc.batch_versioned_data=false ``` 📌 这将 **禁用 Hibernate 对带版本字段的批量更新的行数检查**。 > ⚠️ 后果:可能掩盖并发更新问题,导致“后提交者覆盖先提交者”的数据丢失! 👉 仅用于迁移脚本、历史数据修复等一次性场景,**生产环境慎用!** --- ### ✅ 方案 4:使用 `@DynamicUpdate` 减少不必要的更新 有时即使没改字段,JPA 也会发出 update 语句。 加上注解减少干扰: ```java @Entity @DynamicUpdate public class MyEntity { @Version private Integer version; ... } ``` --- ## ✅ 如何调试? ### 开启 SQL 和更新日志: ```yaml logging: level: org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql.BasicBinder: TRACE ``` 查看输出的 SQL 是否像这样: ```sql UPDATE my_entity SET name=?, version=? WHERE id=? AND version=? ``` 然后检查数据库中是否有多个满足 `id=1 AND version=0` 的记录。 --- ## ✅ 总结 | 原因 | 推荐解决方式 | |------|---------------| | 数据库存在重复主键 | 清理重复数据 | | 并发修改同一条数据 | 使用分布式锁或重试机制 | | 使用 JPQL 批量更新带 `@Version` 的实体 | 改为逐条更新或使用原生 SQL + 手动 version 处理 | | 不需要严格乐观锁控制 | 设置 `hibernate.jdbc.batch_versioned_data=false`(仅限特殊场景) | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值