Session处理性能过慢的问题排查与优化

处理性能过慢的问题排查与优化

一、问题现象

某业务场景,设备发送 DurableOutBindAllRequest 消息后,DurableOutBindAllRequestProcessor.postProcessing() 执行缓慢,主要卡在两个地方:

1. coldPlateBindApi.deleteAll(...) 逐条删除慢

两条删除之间间隔约 2.5 秒:

复制代码
15:22:58.741  delete [ColdPlateBind(...2660030335/M03)]   ← 第 1 条删完
15:23:01.189  doWork  PK=[ColdPlateBindPk(...2660031491/M03)]  ← 第 2 条才开始,隔了 ~2.5s
15:23:01.191  delete [ColdPlateBind(...2660031491/M03)]

另一份日志里 deleteAll 整体 OUT elapse=[5551](5.5 秒删 2 条)。

2. generateEapBindRealtionBySql(...) 里的 update 慢

assingCarrier 内部调用的名称生成,update 前有约 8 秒空档:

复制代码
13:11:10.947  IN : invoke=[EventApiImpl.update] args=[[NameRuleSerialDef(...preFix=P10)]]
13:11:18.725  doWork  PK=[NameRuleSerialDefPk(...preFix=P10)]   ← 隔了 ~8s 才开始 doWork
13:11:18.727  update [NameRuleSerialDef(...preFix=P10)]

二、根因分析

2.1 deleteAll 不是批量删除,而是逐条循环

ColdPlateBindApiImpl 没有自己的实现,直接继承 Core JAR 里的 EventApiImpl。反编译 EventApiImpl.deleteAll(List) 可见,它只是把 List 拿出来一条条循环调用 delete(D):

java 复制代码
// EventApiImpl.deleteAll (反编译)
for (EventData d : list) {
    delete(d);   // 每条都走完整流程
}

单条 delete(D) 每条要执行:

java 复制代码
getClaims();
em.getMetamodel().entityPersister(...).getEntityTuplizer().getIdentifier(...);
repo.findById(id);                 // 一次 DB 查询
event(id, taskInfo, postAction);   // doWork / postAction / addHistory 整套 EventTask 流程
repo.delete(entity);

日志里的 doWork → postAction → addHistory → delete 正是这一条的处理过程。

2.2 真正的瓶颈:大事务导致的 Hibernate autoflush

postProcessing() 整体包在一个 PROPAGATION_REQUIRES_NEW 的大事务里,从 getTransaction(def) 到 commit(status) 之间的所有操作(trackOut、mergeLot、qrCodeRelation/product 的 event、deleteAll、assingCarrier)全部跑在同一个事务、同一个 Hibernate Session(持久化上下文)里。

问题链路:

  1. 事务内没有中途 flush/clear,实体只增不减地累积在持久化上下文里,全是 dirty 状态。
  2. delete / update / find 这类操作执行前,Hibernate 的 FlushMode.AUTO 会对整个膨胀的 Session 做 dirty check + flush。
  3. Session 越大,每次 autoflush 越重。
  4. deleteAll 循环每一圈都会重复触发这个重 flush → 两条删除之间隔了 2.5 秒;generateEapBindRealtionBySql 的 update 前同理隔了 8 秒。

结论:慢不是 SQL 本身慢,而是每一步操作前的 autoflush 在反复扫描肥大的持久化上下文。 这和事务传播类型(REQUIRES_NEW)无关,换成 REQUIRED 也一样,根因是"大事务 → 大 Session → 每次 autoflush 都重"。

2.3 补充:一处冗余查询

postProcessing() 开头已用 coldPlateBindApi.findAllWithEquals(query) 查出待删实体,但 delete(D) 内部又对每条重新 findById 查一遍,查询重复(本次未针对该点改动,因其耗时占比小)。

三、解决方案

保留历史记录(ColdPlateBindHist)的前提下,逐条 event 流程不能省,但可以在进入 deleteAll / assingCarrier 之前主动清空持久化上下文,让后续每步的 autoflush 面对一个干净、轻量的 Session。

核心两行:

java 复制代码
entityManager.flush();   // 先把前面累积的 dirty 变更一次性写库(该写的还是要写,不亏)
entityManager.clear();   // 再清空持久化上下文,后续操作的 autoflush 不必再扫这堆实体

放在 if 外面、deleteAll 和 assingCarrier 之前,保证无论 coldPlateBindResult 是否有数据,assingCarrier 里的 update 都能受益。

为什么有效

  • flush():把前面所有 dirty 实体一次性写库。这次写省不掉(commit 时也要写),但集中做一次。
  • clear():把这些实体从 Session detach。之后 deleteAll 每条 findById、以及 generateEapBindRealtionBySql 的 update 触发 autoflush 时,面对的是一个空 Session,dirty check 立刻返回。
  • 每条 delete 的 addHistory 照常执行,历史记录不受影响。

关于事务与顺序的注意点

  1. 顺序必须 flush 在前、clear 在后 。clear() 会丢弃尚未 flush 的变更;先 flush 保证前面 trackOut/merge/event 的改动已落到数据库,再 clear 只是 detach,不会丢数据。
  2. 对 commit 无负面影响 。flush() 只改变"何时发 SQL",不改变"何时提交";异常时 rollback(status) 仍会把 flush 出去的 SQL 一起回滚,原子性不变。
  3. assingCarrier 保持在 commit 之前的 try 块内。(曾尝试把它移到 commit 之后,会脱离事务、异常时无法回滚,已否决并还原。)
  4. clear() 会 detach 所有实体(含 modingBodyLotList) 。改动后 assingCarrier 只读 getLotName()/getFactoryName() 等已加载字段,内部用 new LotPk(...)/new DurablePk(...) 重新按主键操作,不依赖托管态,安全。
相关推荐
wuyk55539 分钟前
防逆向基础:在量产固件中关闭调试接口并设置读保护(RDP)的正确姿势
stm32·单片机·嵌入式硬件·性能优化·重构
打工仔折腾 AI1 小时前
在 Ubuntu ARM64 部署 Q1 V3.0 MuJoCo 模型:加载、单步推理与执行器响应解析
linux·运维·人工智能·python·深度学习·ubuntu·性能优化
用AI来创业吧11 小时前
企业官网GEO改造:从页面结构到监测回填的五层清单
性能优化·需求分析
乐动心情11 小时前
AI推理性能优化的关键技术即演进路径
人工智能·性能优化
自由能燃气设备13 小时前
【无标题】硅铸铝锅炉漏水只能整机换吗?商用燃气热水锅炉的寿命与 10 年成本账
性能优化
CCblandy1 天前
前缀缓存命中率不靠引擎靠排列:本机仿真 3 轮,易变字段放开头 98.0%→0.0%
人工智能·python·算法·自然语言处理·性能优化
Random Glow1 天前
聚簇索引 vs 二级索引:回表、覆盖索引、索引下推
数据库·mysql·性能优化·innodb·1024程序员节·索引优化
Random Glow1 天前
一条 SQL 从输入到返回,中间经历了什么
数据库·后端·mysql·性能优化·innodb·1024程序员节
Linux实战笔记1 天前
GraphQL vs REST API 设计实测:灵活查询与性能权衡逐项对比
性能优化·graphql·rest api·1024程序员节·dataloader·n+1 查询
余槐i1 天前
开虚拟线程后接口反而更慢:JDK 21 synchronized 锁住载体线程的排查
java·spring boot·redis·mysql·性能优化