Java

一次数据库等待链事故里,面板到底该怎么帮你收敛判断

真正麻烦的数据库等待链事故,不是没有图,而是现场每个人都能从图里讲出一个像样的猜测。沿一次典型夜间事故往下走,才能看清哪些面板只是证据,哪些误判最该先排掉。

  • 监控面板
  • 数据库
  • 锁等待
  • 连接池
  • 稳定性治理
15 分钟阅读

数据库等待链最难受的地方,不是数据库一眼打满,也不是某条 SQL 明晃晃地慢到藏不住。

真正麻烦的现场,往往是这样开始的:

  • 几个核心接口的 RT 先抬起来了
  • 超时开始零星出现,但还没到整站雪崩
  • 连接池 active 往上顶,pending 也在涨
  • 数据库 RT 比平时高了一截
  • 慢 SQL 数量变多了,但又没多到足够“一锤定音”
  • 锁等待有一点,缓存命中率也掉了一点

这时候群里最容易出现的,不是没有判断,而是判断太多:

  • “数据库 CPU 还行,不像实例资源打满。”
  • “慢 SQL 是多了,但还不像根因。”
  • “Hikari pending 上来了,要不要先扩连接池?”
  • “是不是缓存 miss,把回源打高了?”

这些话都不算离谱。问题在于,如果你还是按“固定几屏面板各看各的”来组织判断,现场会很快变成每个人都能从一张图里找到支持自己猜测的证据,但没人能说明白:

这条等待链,最早是从哪里开始积起来的?

我后来把这类文章越写越窄,最后发现真正有用的不是再讲一版“值班时固定看哪几屏”,而是沿一类真实事故往下走:告警怎么起,最先看见什么,为什么会误判,面板在中途到底帮你排掉了什么,最后又是怎么收敛到可执行动作的。

下面这次事故,不是某次线上复盘的逐字记录,但它足够接近我见过的那类典型夜间等待链现场。

一、告警刚起的时候,最危险的不是信息少,而是每个信息都像原因

那次是凌晨后的写链路抖动。

先响的是订单相关接口 RT 告警,随后超时率开始抬,错误率还没完全炸开。第一眼看上去,很像常见的“数据库慢了,应用跟着排队”。

如果只摘几条当时最容易被看到的数据,大概会是这样:

  • 订单写接口 P99 明显拉高
  • 数据库调用 RT 上升
  • 连接池 pending 开始持续增长
  • 慢 SQL 数量比平时多
  • 锁等待也有一些抬头

这类现场最容易把人带进一个错觉:

既然数据库相关指标几乎都在涨,那就从数据库本身开始怀疑。

但我现在碰到这种情况,第一步反而不会立刻扎进数据库内部面板。原因不是数据库不重要,而是如果连影响面都没先看清,后面所有判断都容易把“局部异常”讲成“整库事故”。

我先看的,是服务影响面那块最朴素的图:

  • 哪些接口在抖
  • 是读写一起抖,还是只有一段链路先坏
  • P95、P99 是局部尖刺,还是整体都被拉高
  • 请求量本身有没有突变

这一步很快给了我一个重要结论:

受影响的主要是写链路,读接口整体还算稳。

这意味着,至少当时还不像“数据库整库性能一起掉下去”。如果是整库资源问题、实例级拥塞,读写通常不会分得这么开。

这个结论本身不能告诉我根因,但它先帮我挡住了第一层误判:

现在看到的数据库抖动,未必是数据库整体先坏;它也可能只是某段写事务先卡住,然后把等待往后推。

二、现场最容易做错的动作,就是把结果当原因

影响面看完以后,我第二步才去看应用侧等待。

因为数据库等待链里,一个特别常见的误判就是:

  • 看到线程池排队,就说应用扛不住了
  • 看到连接池 pending 上升,就说连接池太小了
  • 看到数据库 RT 增高,就说数据库先慢了

但这些量,很多时候都只是同一条等待链传导到不同位置后的表现。

那次我把应用侧几条时间线摆到一起看,重点只有几项:

  • 连接池 active / pending
  • 获取连接耗时
  • 请求线程池 active / queue

真正有价值的不是某个绝对值,而是先后顺序

那晚的顺序很清楚:

  1. 连接池 pending 先起来
  2. 获取连接耗时随后变长
  3. 线程池排队再往后出现

这个顺序一下子排掉了一个很容易在群里被喊出来的动作:

先扩线程池,或者先把应用实例数拉起来。

因为如果线程池排队是更后面的现象,它更像是请求卡在数据库连接归还这一步之后,业务线程被拖住了,而不是应用计算本身先扛不住。

同样,看到 pending 上来,也不能直接得出“把连接池调大就好”。

如果连接迟迟归还不了,你只是把连接池扩大,很多时候等于让更多请求一起挤到已经堵住的数据库等待链上,现场只会更乱。

到这一步,我能确认的是:

  • 应用确实在等数据库这一段
  • 线程池拥堵更像后果,不像起点
  • 单纯从应用层扩容,大概率不是先手止血动作

但还有一个关键问题没回答:

数据库这段,到底是执行变慢了,还是等待变长了?

三、很多“数据库慢”,其实不是慢在执行,而是慢在等

这是我现在看数据库内部时最在意的一刀。

因为太多现场会直接跳到慢 SQL TopN,然后开始讨论 explain、索引、执行计划,好像只要数据库 RT 上来,就一定是 SQL 自己跑慢了。

可真实的等待链事故里,应用感知到的“数据库慢”,往往混在一起:

  • 等连接
  • 等锁
  • 等事务释放资源
  • 真正执行 SQL
  • 返回结果

如果不先拆开这个问题,后面的排查方向会很容易歪。

所以那次进数据库相关面板后,我没先盯慢 SQL 排名,而是先看:

  • 活跃事务和长事务有没有抬头
  • 锁等待时间是不是比慢 SQL 更早变化
  • 热点对象是不是集中在少数记录或少数表上
  • 数据库内部执行时间,和应用侧感知到的调用时间,差距有没有突然拉大

这里面最关键的发现有两个。

第一,长事务和锁等待抬头的时间,比慢 SQL TopN 明显更早。

第二,慢 SQL 虽然变多了,但很多 SQL 本身的执行计划并没有突然变坏,它们只是排在等待后面,看起来一起“慢了”。

这一下,现场就从“是不是哪条 SQL 退化了”收窄成了另一个方向:

更像是某类写事务把热点记录占住了,后面的请求不是跑得慢,而是排着队过不去。

这是数据库等待链事故里特别容易看反的一层。

因为慢 SQL 面板太显眼,值班时又天然会盯 TopN,所以人很容易把“排队后变慢的 SQL”误认成“主动制造堵塞的 SQL”。

如果这时候开始大聊索引优化、执行计划,讨论当然也不是完全没价值,但它很可能不是这次现场最先该做的事。

四、缓存和回源面板在这里的作用,不是讲体系建设,而是排除另一条很像的故事

到这一步,方向已经收了不少,但我还要再排掉另一类很常见的假设:

是不是缓存先失效,回源把数据库写链路和连接池一起打高了?

因为这类事故长得也很像:

  • 请求一多,数据库调用 RT 变差
  • 连接池紧张
  • 应用线程被拖住
  • 数据库里也能看到不少“慢”

如果不把这条线排掉,你还是不能放心把注意力放回锁和事务。

所以我才会去看缓存命中率、miss QPS、关键接口回源量、重试量这些图。

注意,这里面板的角色已经不是文章主角了。它们只是在回答一个很具体的问题:

现在是不是有外部流量放大因素,在把数据库等待继续往上推?

那晚看下来,几个结论都比较干脆:

  • Redis 命中率没有出现断崖式下滑
  • miss QPS 有轻微波动,但不够解释整段等待链
  • 关键写接口的回源量没有异常放大
  • 重试量也还没到把系统自己放大的程度

这等于把“缓存先坏,数据库被动吃流量”这条故事先排掉了。

当一条可能性被排掉以后,面板的价值就体现出来了:

它不是告诉你世界的全貌,而是帮你少走一段弯路。

五、真正收敛的瞬间,往往不是看见了新指标,而是终于敢把别的解释放下

影响面、应用等待、数据库内部等待、缓存回源这些线索摆到一起之后,那次现场其实已经能说出一条比较连贯的因果线了:

  1. 先出问题的是订单写链路,不是整库读写一起崩
  2. 应用侧最早出现的是连接池等待,不是线程池本身先扛不住
  3. 数据库内部更早抬头的是长事务和锁等待,不是执行计划突然恶化
  4. 缓存和回源没有明显异常,外部放大量不成立

剩下的方向就很集中:

去找那段把写事务拖长、把热点记录卡住的东西。

最后顺着事务和锁对象往下查,定位到的是夜间批任务和订单写链路在一段热点数据上撞在了一起。

它不是那种“数据库资源炸满”的事故,也不是“缓存雪崩”的事故,更不是“某条 SQL 突然没走索引”的事故。

更准确地说,它是一次很典型的等待链事故:

  • 某类写事务持锁时间变长
  • 后续写请求开始排队
  • 连接归还变慢
  • 连接池 pending 上升
  • 业务线程逐渐被拖住
  • 应用 RT 和超时一起变差

真正值钱的动作,也不是当场把几十张图看得更细,而是很快做两个收敛动作:

  • 先把夜间批任务错峰,别继续和在线写流量硬撞
  • 再回头拆那段热点更新,把持锁时间和竞争范围压下去

前者是止血,后者才是治理。

如果那晚一路盯着慢 SQL TopN 走,最后很可能会把事故讨论成一次索引优化会;如果一路盯着连接池,又可能把动作带成“先扩容再说”。

这两件事都不一定完全错,但都不是当时最先该做的。

六、我现在怎么理解“值班面板”这件事

所以我后来越来越少讲“固定几屏面板怎么排”。

不是这些面板不重要,而是如果文章的主结构变成“第一屏看什么,第二屏看什么,第三屏放什么组件”,读者最后记住的通常只是一个排版方案,或者一套值班入口清单。

可数据库等待链真正难的,从来不是图不够全,而是现场太容易被带偏。

更实用的理解应该是:

  • 影响面板,帮你先分清是局部链路还是系统性事故
  • 应用等待面板,帮你确认应用是在制造问题,还是在承受问题
  • 数据库内部面板,帮你分清慢在执行还是慢在等待
  • 缓存和回源面板,帮你排掉“数据库只是被外部放大拖高”的可能

也就是说,面板存在的意义,不是为了组成一套“标准值班分屏方法论”,而是为了在事故现场一层层排掉误判。

当你这样用它们时,图就不再是主角了。

真正的主角,是那条因果线:

哪一块先卡住,等待怎么传下去,哪些现象只是后果,哪里才是最值得先动手的地方。

七、如果只记一句话,我希望是这个

数据库等待链事故最怕的,不是没面板,而是每个人都能从面板里看到自己想看的东西。

所以比“多看几张图”更重要的,其实是让自己沿着事故往下问:

  • 最早坏的是影响面,还是局部链路?
  • 应用是在放大问题,还是在承受问题?
  • 数据库是执行慢了,还是等待变长了?
  • 有没有别的流量放大因素,在伪装成数据库故障?

这些问题答清以后,面板自然会变成证据。

而不是文章本身。