news 2026/5/16 11:23:27

蓝凌EKP产品:一次 Hibernate 乐观锁 + 死锁的深度踩坑实录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
蓝凌EKP产品:一次 Hibernate 乐观锁 + 死锁的深度踩坑实录

—— clear() 一个集合,为什么引发 OptimisticLockException 和数据库死锁?

这是一次看似“新增 / 查询”的普通业务操作,却最终演变成
Hibernate 乐观锁异常 + MySQL 死锁 + 批量更新失败的连环事故。

一、问题现象

线上频繁出现如下异常:

javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

伴随的数据库日志还有:

Deadlock found when trying to get lock; try restarting transaction SQL: delete from km_agreement_review_areader where fd_source_id=?

诡异点在于:

  • 表面调用的是新增审计意见 / 新增参数

  • 堆栈中指向的是find()查询

  • 实际报错却是update / delete

  • 涉及的表包括:

    • km_agreement_review_areader

    • lbpm_execution

怎么看都不像是一个地方的问题。

二、第一个误区:find / insert 不会触发乐观锁?

这是一个非常常见的误区。

在 Hibernate / JPA 中,只要触发了 flush,
所有被托管(managed)的实体都会被统一同步到数据库。

也就是说:

insert A find B

在 Hibernate 内部真实执行顺序是:

flush Session ├─ update / delete / insert(之前积攒的) └─ 然后才是 find B

异常只是“挂”在 find 上,
真正出问题的是 flush 里的 update / delete。

三、真正的导火索:BaseAuthModel 里的clear()

所有业务 Model 都继承了一个公共父类:

public abstract class BaseAuthModel { protected List authAllReaders; protected void recalculateReaderField() { if (authAllReaders == null) { authAllReaders = new ArrayList(); } else { authAllReaders.clear(); // 关键代码 } // 重新 add 各种 reader authAllReaders.add(getDocCreator()); ... } }

这一行代码,看起来极其“正常”:

authAllReaders.clear();

但在Hibernate 眼里,这是一个非常危险的操作

四、Hibernate 视角:clear() 到底干了什么?

假设映射关系是:

@OneToMany @JoinColumn(name = "fd_source_id") private List<Reader> authAllReaders;

那么:

authAllReaders.clear();

在 Hibernate 中等价于

delete from km_agreement_review_areader where fd_source_id = ?

如果接下来你又:

authAllReaders.add(...) authAllReaders.add(...)

Hibernate flush 时会执行:

delete from km_agreement_review_areader where fd_source_id = ? insert into km_agreement_review_areader ... insert into km_agreement_review_areader ...

五、为什么会死锁?

关键点:fd_source_id是外键

当多个并发请求同时操作同一个业务对象时:

  • 线程 A:clear → delete(持有子表行锁)

  • 线程 B:clear → delete(等待)

  • 线程 A:后续 update 父表 / execution

  • 线程 B:持有另一批锁

👉典型的 InnoDB 死锁模型

DELETE 子表 → UPDATE 父表 → DELETE 子表(另一个事务)

六、为什么又会触发 OptimisticLockException?

系统中只有一个地方配置了 version

<version name="lockerVersion" column="fd_locker_version" type="long"/>

配置在:lbpm_execution表。

而问题在于:

  • lbpm_execution在流程执行时早已被加载进 Session

  • clear / add Reader 的过程中:

    • execution 被标记为 dirty

  • flush 时 Hibernate 会顺带执行:

update lbpm_execution set fd_locker_version = fd_locker_version + 1 where fd_id = ? and fd_locker_version = ?

如果前面发生了:

  • 死锁回滚

  • 并发已更新

  • 行被别的事务影响

👉 update 0 行
👉 Hibernate 判定并发冲突
👉 抛OptimisticLockException

七、为什么堆栈看起来“很乱”?

因为这是统一 flush 导致的错觉

  • 报错点挂在find()

  • 实际执行的是:

    • delete 子表

    • update execution

  • Hibernate不会告诉你是哪一个实体被判定 dirty

这也是 Hibernate 最反直觉的地方之一。

八、问题的本质总结(一句话)

这是一个典型的:
ORM 管理集合 + clear + 并发 + version
共同作用下的“架构级事故”

九、解法一(最推荐):绕过 ORM 管理这个集合

❌ 错误姿势(当前做法)

@OneToMany private List authAllReaders;

并对其:

clear() + add()

✅ 正确姿势 1:不做批量异常在新增,只做增量

这里有现成方案,可以关注后续.....,
public void update(IBaseModel modelObj) throws Exception { IFieldsCalculator fieldsRecalculator = modelObj.getFieldsCalculator(); if(fieldsRecalculator!=null){ fieldsRecalculator.recalculateFields(); }else{ modelObj.recalculateFields(); } //modelObj.recalculateFields(); afterRecalculateFields(modelObj); getHibernateTemplate().saveOrUpdate(modelObj); }

model 实现接口IFieldsCalculator fieldsRecalculator = modelObj.getFieldsCalculator();完成model 的可阅读者的增量更新。

十、为什么这是“无解但必须改”的问题?

因为:

  • BaseAuthModel 是全局父类

  • clear() 是高频路径

  • 并发下必然出现:

    • 死锁

    • 乐观锁冲突

  • Hibernate 在这里帮不了你

👉必须从 ORM 设计上规避

十一、最终总结

Hibernate 非常适合“领域模型”
但需谨慎操作“权限计算 / 批量 clear + 重建”这种模型

这次问题的真正收获不是“修一个 bug”,而是:

  • 明确了哪些集合不该交给 ORM 管

  • 明确了clear() 是 ORM 世界的核按钮

  • 明确了version 不该用在流程执行上下文上

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/14 17:16:30

Excalidraw热力图模拟:用户行为分布示意

Excalidraw热力图模拟&#xff1a;用户行为分布示意 在产品设计的日常讨论中&#xff0c;你是否曾遇到这样的场景&#xff1f;产品经理拿着一份PPT中的热力图说&#xff1a;“这个按钮点击率很低”&#xff0c;但团队成员却一脸困惑——因为那张图脱离了真实界面语境&#xff0…

作者头像 李华
网站建设 2026/5/15 11:30:53

Excalidraw文档编写规范:Markdown语法与示例

Excalidraw 与 Markdown 协同写作实践指南 在远程协作日益频繁的今天&#xff0c;技术团队对“高效沟通”和“知识沉淀”的需求达到了前所未有的高度。我们常常遇到这样的场景&#xff1a;一个复杂系统的设计思路&#xff0c;在会议中讲得头头是道&#xff0c;但会后整理文档时…

作者头像 李华
网站建设 2026/5/13 21:23:21

Excalidraw负载均衡配置:高并发场景下的稳定性保障

Excalidraw负载均衡配置&#xff1a;高并发场景下的稳定性保障 在远程协作成为常态的今天&#xff0c;团队对实时交互工具的需求早已超越“能用”层面&#xff0c;转而追求稳定、低延迟、可扩展的协作体验。Excalidraw 作为一款开源手绘风格白板工具&#xff0c;凭借其极简设计…

作者头像 李华
网站建设 2026/5/13 21:22:32

Excalidraw对齐辅助线触发距离设置建议

Excalidraw 对齐辅助线触发距离设置建议 在设计工具的世界里&#xff0c;一个看似微不足道的像素值&#xff0c;往往能决定整个用户体验的流畅与否。比如你在拖动一个方框时&#xff0c;它是否“恰到好处”地贴合到另一个元素边缘——这种直觉般的精准感&#xff0c;背后其实依…

作者头像 李华
网站建设 2026/5/13 21:22:54

Excalidraw自由绘图平滑度优化:手写轨迹处理算法

Excalidraw自由绘图平滑度优化&#xff1a;手写轨迹处理算法 在数字白板工具日益普及的今天&#xff0c;用户早已不再满足于“能画”&#xff0c;而是追求“画得自然”。尤其是在远程协作、头脑风暴或教学演示场景中&#xff0c;一条流畅、有笔触感的手绘线条&#xff0c;往往比…

作者头像 李华
网站建设 2026/5/13 21:22:41

为什么你的努力领导看不到?是你不会向上管理,想要优秀,至少要做到第三层级

底层是被动响应,领导安排什么做什么,结果是没存在感; 第二层是主动汇报,定期反馈进展,但只是执行者; 第三层是提前预判,不只汇报还提建议,领导觉得你靠谱; 第四层是影响决策,用数据影响领导,成为智囊; 顶层是成为伙伴,理解领导压力主动分担,领导把你当自己人。 大多数人停在第二…

作者头像 李华