Java

慢 SQL 修完了,接口还是慢,怎么做全链路复核?

慢 SQL 修完以后接口还慢,往往不是优化没生效,而是问题根本不只在 SQL。要重新对齐的,是数据库 RT、连接池、线程池、缓存回源、RPC 依赖和超时预算这些环节里,哪一段真的降了,哪一段只是换了地方继续卡。

  • 慢SQL
  • 接口慢
  • 全链路排查
  • MySQL
  • 性能排查
16 分钟阅读

很多慢接口排查,都会走到一个很让人挫败的时刻:

  • 慢 SQL 已经找到了
  • 执行计划也优化了
  • 慢日志确实下来了
  • 但接口 RT 还是没有按预期回落

这时候团队里最常见的两种反应是:

  • 要么怀疑 SQL 根本没修好
  • 要么开始怀疑“数据库优化没有用”

这两种判断都不够稳。

因为线上真实情况经常是:慢 SQL 只是整条慢链上的一个节点,不一定是唯一节点。

也就是说,SQL 修完以后,接口还慢,至少有四种完全不同的可能:

  • SQL 根本没真的修到主因,慢点还在数据库里
  • SQL 是修了,但连接池、锁等待、事务边界还没恢复
  • 数据库这段已经好了,慢点转移到了缓存回源、RPC 依赖或线程排队
  • 指标看起来好了,但优化并没有真正穿透到线上流量和主路径

所以更值得先问的问题不是:

  • 为什么 SQL 修了还没用?

而是:

SQL 修完以后,整条接口耗时里,哪一段真的降了,哪一段没降,哪一段反而因为链路重分布暴露出来了?

如果慢 SQL 已经修了,但接口还是慢

你现在看到的现象更适合先看哪条线为什么
慢 SQL 已经优化,慢日志也下来了,但接口 RT 还没恢复继续看本文这就是本文要处理的“修复后复核”场景
你还没确认问题是不是数据库阶段导致的先回接口慢总排查或数据库等待链那篇先把链路收窄再复核
数据库 RT、锁等待、连接池仍然明显异常先去数据库 RT / 锁等待 / 长事务相关文章先确认数据库瓶颈是否真的离场
你想沉淀固定值班看板和复盘面板先看等待链监控面板页那篇更偏治理建设

这篇主要接哪类现场

如果你已经做完 SQL 优化、索引调整或执行计划治理,但接口 RT 还是没按预期回落,这篇就正好接得上。

它不是重新带你定位慢 SQL,也不是新的数据库入口;这里更像一次修复后的复核:数据库这段到底有没有真的从主链路上退场,还是只是慢日志先变好看了。

如果你还没定位慢点,这篇不该先看;如果你已经做了修复但系统没恢复,就该拿它继续收口。

先核对真正降下来的那一段

  1. 先把优化前后的 SQL 执行时间、数据库调用 RT 和接口端到端 RT 摆在一起。
  2. 再确认优化是否真的命中了线上主流流量,而不是只修好了样本 SQL。
  3. 如果数据库阶段恢复了,但线程池、连接池、缓存回源或 RPC 还没恢复,就别再只盯 SQL。
  4. 如果数据库 RT 仍高,先回数据库 RT、锁等待和长事务链路继续查。
  5. 最后把超时预算、重试和上游放大器重新放进同一条时间线复核。

一、先别急着怀疑“优化没生效”,先确认你到底在验证什么

很多团队说“慢 SQL 修完了,接口还是慢”,其实验证口径混了至少三层:

  • SQL 单次执行时间是不是降了
  • 数据库调用 RT 是不是降了
  • 接口端到端 RT 是不是降了

这三件事不是同一个指标。

1. SQL 执行时间下降

说明数据库内部某条语句确实更快了。

2. 数据库调用 RT 下降

说明优化开始穿透到了应用侧数据库阶段。

3. 接口端到端 RT 下降

说明慢 SQL 真的是全链路主瓶颈,或者至少没有其他更大的等待点继续卡在前面。

如果只看到第 1 条成立,第 3 条没明显变化,并不能直接说明 SQL 优化无效。很多时候只是说明:

  • 数据库这段降了,但全链路还有别的段更慢

二、第一步先做差分:到底是哪一段降了,哪一段没降

这一步特别重要。

最稳的做法不是继续争论,而是把优化前后几个关键阶段放在一起对比:

  • 接口总 RT
  • 数据库调用 RT
  • 获取连接耗时
  • 事务总时长
  • 下游 RPC 调用耗时
  • 缓存 miss / 回源耗时
  • 线程池排队时间

只有做了这一步,才能判断下面哪种情况更接近真实:

场景 1:数据库调用 RT 真降了,但接口 RT 没怎么降

更像:

  • 还有其他环节更慢
  • 或者数据库只占总耗时的一小段

场景 2:SQL 单次执行降了,但数据库调用 RT 没明显降

更像:

  • 锁等待、长事务、连接池等待还在
  • 数据库层问题并没有只靠改语句就完全解决

场景 3:所有数据库指标都降了,但只有部分接口恢复

更像:

  • 主路径和观测样本不一致
  • 只有某类参数、某类实例、某类流量还在走慢链

三、慢 SQL 修了,接口还是慢,最常见的第一类原因:锁和事务链没跟着恢复

这是最常见的一类。

团队往往会盯住 SQL 文本,但真正拖慢接口的,有时是:

  • 锁等待
  • 长事务
  • 热点行冲突
  • 事务里夹下游调用

为什么会出现这种现象

因为慢 SQL 优化之后,执行期时间可能确实短了,但如果:

  • 前面仍然要等锁
  • 后面事务仍然迟迟不提交
  • 连接仍然被拿得很久

那应用看到的数据库调用 RT 和接口 RT 就不一定明显恢复。

典型现场会是:

  • explain 比以前好了
  • 慢日志也少了
  • 但连接池等待还在
  • active sessions 还高
  • 写接口仍然抖

这时就要顺着锁等待和事务链继续排,而不是只在 SQL 语句本身打转。

四、第二类原因:数据库阶段降了,但线程池和连接池还在排队消化旧压力

还有一种很常见的情况是:

  • SQL 已经更快了
  • 但系统仍然在高峰或故障后的“排队后遗症”里

比如前面已经发生过:

  • 线程池堆积
  • 连接池打满
  • 上游重试放大
  • 缓存回源冲击

此时即使数据库这段修好了,系统也不一定立刻恢复到你期待的 RT。

因为还存在:

  • 排队中的旧请求
  • 线程池队列消化时间
  • 连接持有恢复滞后
  • 上游重试造成的额外调用量

这时如果只盯住“SQL 已经快了,为什么接口还慢”,会忽略真正的问题已经从数据库主因变成了等待链余震。

五、第三类原因:主瓶颈其实已经转移到了下游依赖或 RPC 中间层

这类特别常见于你一开始只盯数据库的场景。

很多链路并不是“只有数据库慢”,而是:

  • 数据库慢
  • 缓存回源也高
  • 下游 RPC 调用也在抖
  • 线程池和连接池已经一起变紧

数据库优化以后,最显眼的慢点可能消失了,但剩下的次主因开始浮出水面。

于是你会看到:

  • 数据库 RT 降了
  • SQL 也不算主问题了
  • 但接口还是慢
  • trace 里大头时间开始出现在别的 span

常见的新暴露点包括:

  • 某个 RPC 依赖 timeout
  • Netty EventLoop 或回调线程拥堵
  • 连接池等待仍在高位
  • 缓存 miss 后回源重建很慢

这类场景里,SQL 优化不是没价值,而是它把原本被数据库掩盖的第二个瓶颈暴露了出来。

六、第四类原因:优化只对样本生效,没有真正覆盖线上主流请求

还有一类误判来自验证样本本身。

比如:

  • 你修的是某一条典型 SQL
  • 线上真正拖慢接口的是另一类参数组合
  • 优化只覆盖了主库,慢的其实发生在只读库或分库分表某一段
  • 压测样本和线上主流租户、主流业务键不一致

这时候你会看到:

  • 某条 SQL 监控确实漂亮了
  • 但接口 RT 改善不明显
  • 继续下钻才发现真正的热点流量走的不是这条路径

所以“修完了”这件事一定要回到主路径和真实流量上验证。

七、我在现场通常这样做全链路复核

如果以后再碰到“慢 SQL 修完了,接口还是慢”,我更建议按下面顺序做全链路复核。

第 1 步:先确认 SQL 优化是否真的在线上主流流量生效

重点看:

  • 优化前后同一类 SQL 的 rows、RT、调用次数
  • 是否覆盖到了主流参数、主流租户、主流实例
  • 有没有只优化到边缘路径

第 2 步:再看数据库阶段是否真的恢复

重点看:

  • 数据库调用 RT
  • 锁等待
  • 活跃事务
  • 获取连接耗时
  • active sessions

如果这些还在高位,就不要急着跳出数据库线。

第 3 步:再看事务和连接池是否还在拖后腿

重点看:

  • 事务总时长
  • 连接持有时间
  • 连接池等待线程数
  • 有没有事务里夹下游调用

第 4 步:把线程池、缓存和 RPC 依赖拉进来

重点看:

  • 线程池队列和执行等待
  • 缓存 miss、回源量、回填时间
  • RPC timeout、重试、中间层耗时

第 5 步:最后再核对超时预算和上游放大器

重点看:

  • 上游有没有重试
  • 网关和客户端 timeout 是否仍然太紧
  • 是否已经从“数据库慢”转成“超时扩散”问题

八、一个典型案例:为什么 SQL 已经从 800ms 优化到 60ms,接口还是要 1.8s

假设订单详情接口一开始主要被一条查询拖慢。

优化前

  • SQL 耗时 800ms
  • 接口 RT 2.4s
  • 连接池等待明显
  • 缓存回源量偏高

优化后

  • SQL 降到 60ms
  • 慢日志明显变少
  • 但接口 RT 仍然在 1.8s 左右

继续复核后发现

  • 连接池获取连接仍要 300ms
  • 事务里还有一次下游营销 RPC 调用,要 700ms
  • 缓存 miss 后回填时间偏长
  • 上游还保留着超时重试

最后的真实结论

慢 SQL 确实修掉了第一瓶颈,但接口慢链还剩: 连接等待 + 下游 RPC + 缓存回填 + 重试放大

这个案例特别典型地说明:修掉一条慢 SQL,不等于整条接口慢链自动消失。

九、最容易出现的几个误判

误判 1:慢日志下降了,接口就一定该同步恢复

不一定。

数据库只是链路的一段,别的等待点仍可能存在。

误判 2:接口没恢复,就说明 SQL 优化没价值

也不对。

它可能已经把主瓶颈移走,只是第二瓶颈开始暴露。

误判 3:只看单条 SQL,不看连接池、事务和接口时间结构

这会让你误把“局部优化成功”当成“全链路一定恢复”。

误判 4:压测样本跑通了,就默认线上主路径也恢复了

如果样本不在真正热点路径上,验证结论会很容易失真。

十、常见追问

1. 怎么最快判断问题是不是还在数据库里?

看数据库调用 RT、锁等待、活跃事务、连接池获取连接时间。如果这些还没明显回落,问题大概率还没真正走出数据库等待链。

2. SQL 已经很快了,为什么连接池还是紧?

因为连接池看的是连接持有总时长,不只是 SQL 执行时间。事务边界、锁等待、下游调用都可能继续把连接拿很久。

3. 为什么修完数据库问题后,RPC timeout 反而更显眼了?

因为数据库不再是最显眼的瓶颈后,原本被遮住的 RPC 中间层或下游依赖问题开始暴露。

4. 最值钱的验证动作是什么?

不是只看某一个 SQL 图,而是把接口总 RT、数据库 RT、连接池、线程池、缓存回源和 RPC span 放到同一个时间轴上做差分。

十一、如果数据库这段看起来已经松了,再往下补这些

如果你已经确认慢 SQL 不再是唯一显眼的瓶颈,我现场通常会先盯着剩下那几个还在抬头的指标选下一篇,而不是把数据库文章再从头翻一遍。

数据库这段还没完全放掉时

数据库之外常接着看的两段

你现在更该往哪边补查

  1. 如果数据库 RT、锁等待和连接池还没回落,先看 数据库 RT 抬高但慢 SQL 不明显,先查锁、线程还是 I/O?
  2. 如果数据库阶段已经恢复,但接口还是慢,就别再盯着 SQL 图打转,直接转去看缓存回源和 RPC 依赖相关文章
  3. 如果想把整条接口慢链重新从入口过一遍,再看 接口响应慢怎么排查?后端性能问题定位步骤

十二、最后总结:慢 SQL 修完以后,最值钱的不是继续盯 SQL,而是确认瓶颈有没有真的离开主要链路

慢 SQL 修完了,接口还是慢,这类现场最容易误判的地方就在于:

  • 以为数据库优化一定直接等于接口恢复
  • 或者反过来,以为接口没恢复就说明数据库优化白做了

更稳的排查主线应该是:

先确认 SQL 优化是否命中真实主路径,再看数据库等待链有没有恢复,然后把连接池、线程池、缓存回源、RPC 依赖和超时预算一起拉回同一条时间线做复核。

只要这条顺序不乱,你就能更快分清:到底是慢 SQL 还没真修好,还是主瓶颈已经悄悄转移到了别的环节。