长事务明明已经修了,连接池还是很紧时,我怎么确认问题还卡在哪一段?
一次对账链路优化后,事务时长已经明显下降,但连接池和 pending 还是高位。真正有用的不是立刻否定修复,而是把事务时长、持连接时间、锁等待和 backlog 的回落顺序放到一个时间窗里复核。
那次对账链路做完优化以后,最让人烦躁的不是改动本身,而是改完之后监控还不好看。
我们已经做了三件看起来都对的事:
- 把事务里的远程账单查询挪到了事务外
- 把一段批量明细拼装改成异步
- 把最慢的补写逻辑拆成了后置任务
按理说,长事务该松了。事实也确实如此:事务总时长从秒级掉回了几百毫秒。
可 20 分钟后看监控,连接池还是紧:
- Hikari active 还是贴着上限
pending还在高位- 获取连接 p95 还是比平时高很多
这时候特别容易冒出来两种话:
- 长事务根本没修好
- 连接池和长事务没关系
可那次真正有用的动作,不是立刻选其中一种,而是先复核:这次修掉的,到底是事务长,还是连接真正被占住的那段长。
我先把优化前后 4 组指标放进同一个时间窗
20:02 做完灰度后,我先看下面这几项:
| 指标 | 优化前 | 优化后 10 分钟 |
|---|---|---|
| 事务总时长 p95 | 3.2s | 620ms |
| 连接持有时长 p95 | 3.4s | 2.1s |
| 获取连接 p95 | 780ms | 430ms |
Hikari pending | 26 | 17 |
这张表很关键,因为它马上说明:事务是短了,但连接并没有按同样比例变短。
如果只看事务时长,你很容易乐观;但连接持有还在 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 age | 8.1s | 1.1s |
| lock wait sessions | 18 | 4 |
| active sessions | 64 | 41 |
| MySQL CPU | 32% | 29% |
这组数字说明两件事:
- 长事务主因确实被打掉了一大截,尤其是 transaction age 和 lock wait。
- 但 active sessions 还不算低,说明系统还在消化前一波堵车,或者已经有别的慢段顶上来。
所以“连接池还紧”不等于“事务没修到”,更可能是 修掉了主因,但没把后续放大器一起收掉。
那次真正没恢复的,是 backlog 和事务外等待
继续看应用侧,问题就更明显了:
| 指标 | 优化前 | 优化后 10 分钟 |
|---|---|---|
| 请求 backlog | 0 | 143 |
| 账单服务 RT p95 | 1.7s | 720ms |
| 补写确认耗时 p95 | 1.1s | 690ms |
| Hikari active | 39 / 40 | 34 / 40 |
也就是说,连接池没有完全恢复,不是因为长事务还像原来一样严重,而是因为:
- backlog 还在吃旧压力
- 账单服务虽然好转,但还不够快
- 补写确认仍然拖慢总吞吐
连接池此时更像是被整条请求链的恢复速度拖着,而不是继续被原先那个长事务完全锁死。
回退和补动作之后,回落顺序让我更敢确认这件事
20:10,我们先把补写确认彻底改成 fire-and-forget;20:12,再把账单服务查询结果加上短 TTL 本地缓存。
后面几分钟里的回落顺序是这样的:
| 时间 | 先回落的指标 | 后回落的指标 |
|---|---|---|
| 20:11 | backlog、账单服务 RT | - |
| 20:12 | Hikari pending、获取连接 p95 | - |
| 20:13 | reconcile 接口 p95 | active sessions |
| 20:14 | 用户投诉量 | 基本恢复 |
如果我前面理解错了,问题其实还主要卡在数据库锁和长事务里,那最先回落的应该还是 transaction age 和 lock wait。可这时更先掉的是 backlog 和事务外等待,说明主因阶段已经变了。
所以这类“长事务修了但连接池还紧”的现场,我现在先复核这几件事
- 事务时长和连接持有时长,是不是一起下降了?
transaction age、lock wait已经下来了没有?- backlog、重试、事务外 RPC / MQ / 补写链路是不是还在高位?
- 回退或补动作后,最先回落的是数据库等待,还是事务外慢链?
把这四件事对齐,很多“修了怎么还不好”的困惑就会散掉。
那次最后的结论不是“长事务白修了”,而是更具体的一句:长事务确实修到了,但连接池还紧,是因为系统还在消化旧压力,而且新的事务外慢链还在拖总吞吐。
这两者不是互相否定,而是同一条恢复链上的前后两段。