Java

长事务明明已经修了,连接池还是很紧时,我怎么确认问题还卡在哪一段?

一次对账链路优化后,事务时长已经明显下降,但连接池和 pending 还是高位。真正有用的不是立刻否定修复,而是把事务时长、持连接时间、锁等待和 backlog 的回落顺序放到一个时间窗里复核。

  • 长事务
  • 数据库连接池
  • MySQL
  • 性能排查
  • 稳定性治理
12 分钟阅读

那次对账链路做完优化以后,最让人烦躁的不是改动本身,而是改完之后监控还不好看。

我们已经做了三件看起来都对的事:

  • 把事务里的远程账单查询挪到了事务外
  • 把一段批量明细拼装改成异步
  • 把最慢的补写逻辑拆成了后置任务

按理说,长事务该松了。事实也确实如此:事务总时长从秒级掉回了几百毫秒。

可 20 分钟后看监控,连接池还是紧:

  • Hikari active 还是贴着上限
  • pending 还在高位
  • 获取连接 p95 还是比平时高很多

这时候特别容易冒出来两种话:

  • 长事务根本没修好
  • 连接池和长事务没关系

可那次真正有用的动作,不是立刻选其中一种,而是先复核:这次修掉的,到底是事务长,还是连接真正被占住的那段长。

我先把优化前后 4 组指标放进同一个时间窗

20:02 做完灰度后,我先看下面这几项:

指标优化前优化后 10 分钟
事务总时长 p953.2s620ms
连接持有时长 p953.4s2.1s
获取连接 p95780ms430ms
Hikari pending2617

这张表很关键,因为它马上说明:事务是短了,但连接并没有按同样比例变短。

如果只看事务时长,你很容易乐观;但连接持有还在 2 秒以上,说明真正拖住连接的边界,并没有完全跟着事务一起缩短。

为什么我没有立刻判成“修复失败”

因为优化后还有两类现象要区分清楚:

第一类:旧压力还没散完

如果前面已经排了一波很长的队,事务刚缩短,连接池也不会在同一秒立刻健康。旧请求、旧锁等待、旧 backlog 都需要一点时间消化。

第二类:第二瓶颈已经顶上来

这次把事务内的慢账单查询拿出去了,但请求总路径没完全变短。如果线程还是会在事务外等账单服务、等补写确认,系统总吞吐未必马上恢复,连接池也可能继续被别的阶段拖着偏紧。

所以那次我没有马上否定修复,而是继续往下看:连接池现在到底紧在哪一段。

真正把问题拆开的,是一条优化后的 traceId

我抓了一条优化后的慢请求:traceId=2c15db4a9e1f4326

时间点事件
20:07:11.020请求进入 reconcile-service
20:07:11.048调账单服务查外部对账信息
20:07:11.804账单服务返回
20:07:11.829开事务
20:07:11.862拿到连接
20:07:12.116更新对账状态
20:07:12.231提交事务
20:07:12.954等补写确认日志结束
20:07:13.008返回响应

看这条 trace,就知道为什么事务变短了,连接池却没有完全松下来。

事务内那段确实被切短了,可请求总路径里,账单服务还是慢,补写确认还是慢。于是系统吞吐恢复得不够快,上游请求继续堆,后面的拿连接请求仍然会排队。

换句话说:这次修复有效,但它只拆掉了事务内等待,没有把整条慢链全部切断。

数据库侧也能看出:主因在回落,但余震还在

优化前后数据库侧的几组指标长这样:

指标优化前优化后 10 分钟
oldest transaction age8.1s1.1s
lock wait sessions184
active sessions6441
MySQL CPU32%29%

这组数字说明两件事:

  1. 长事务主因确实被打掉了一大截,尤其是 transaction age 和 lock wait。
  2. 但 active sessions 还不算低,说明系统还在消化前一波堵车,或者已经有别的慢段顶上来。

所以“连接池还紧”不等于“事务没修到”,更可能是 修掉了主因,但没把后续放大器一起收掉。

那次真正没恢复的,是 backlog 和事务外等待

继续看应用侧,问题就更明显了:

指标优化前优化后 10 分钟
请求 backlog0143
账单服务 RT p951.7s720ms
补写确认耗时 p951.1s690ms
Hikari active39 / 4034 / 40

也就是说,连接池没有完全恢复,不是因为长事务还像原来一样严重,而是因为:

  • backlog 还在吃旧压力
  • 账单服务虽然好转,但还不够快
  • 补写确认仍然拖慢总吞吐

连接池此时更像是被整条请求链的恢复速度拖着,而不是继续被原先那个长事务完全锁死。

回退和补动作之后,回落顺序让我更敢确认这件事

20:10,我们先把补写确认彻底改成 fire-and-forget;20:12,再把账单服务查询结果加上短 TTL 本地缓存。

后面几分钟里的回落顺序是这样的:

时间先回落的指标后回落的指标
20:11backlog、账单服务 RT-
20:12Hikari pending、获取连接 p95-
20:13reconcile 接口 p95active sessions
20:14用户投诉量基本恢复

如果我前面理解错了,问题其实还主要卡在数据库锁和长事务里,那最先回落的应该还是 transaction age 和 lock wait。可这时更先掉的是 backlog 和事务外等待,说明主因阶段已经变了。

所以这类“长事务修了但连接池还紧”的现场,我现在先复核这几件事

  1. 事务时长和连接持有时长,是不是一起下降了?
  2. transaction agelock wait 已经下来了没有?
  3. backlog、重试、事务外 RPC / MQ / 补写链路是不是还在高位?
  4. 回退或补动作后,最先回落的是数据库等待,还是事务外慢链?

把这四件事对齐,很多“修了怎么还不好”的困惑就会散掉。

那次最后的结论不是“长事务白修了”,而是更具体的一句:长事务确实修到了,但连接池还紧,是因为系统还在消化旧压力,而且新的事务外慢链还在拖总吞吐。

这两者不是互相否定,而是同一条恢复链上的前后两段。