资讯动态

凌晨告警实战:MySQL锁等待与连接池耗尽全复盘

发布时间:2026/10/13 7:52:04 来源:尧图企业网站定制
凌晨2点07分床头柜上的手机开始疯了一样震动。我眯着眼划开屏幕第一眼看到的是监控群里刺眼的红色告警订单接口成功率跌破80%P99响应时间超过5秒。那一瞬间血压确实上来了。做过后端、运维或者独立维护过线上系统的人都懂凌晨的告警远比白天棘手——你面对的是一套已经连续运转数小时的复杂系统、一个刚被吵醒的脑子以及大概率只有你一个人在线的现实。这篇文章就记录这次完整的事故处理过程。从接到告警、确认级别、排查链路到最后找到根因、恢复服务、写复盘报告我把每一分钟做了什么、为什么这么做、踩过的坑都拆开来讲。这不是什么高深理论全是在真实生产环境下反复验证过的方法。无论你是刚接手线上系统的新人还是已经被压出经验的值班老手这套思路和后面的排查速查表都值得存一份。1. 凌晨告警为什么偏偏是2点出事1.1 那个时刻到底发生了什么我负责的系统是某零售平台的订单中台每天处理几十万笔订单流量高峰在白天。凌晨2点理论上是全天最低谷业务基本没有人工操作剩下的主要是系统内部任务在跑比如日终对账、数据归档、报表计算。告警内容很简单订单查询接口的成功率掉到80%以下平均响应时间飙到5秒以上。这个指标平时稳定在99%和200毫秒左右出现这种情况只有一种可能——核心链路某个环节被卡死了。从被吵醒到坐进书房打开电脑我大概用了不到两分钟。查监控面板、看最近15分钟的趋势曲线这是第一个习惯动作。因为告警只是一个信号趋势图才能告诉你事故是从什么时候开始的、范围有多大、是慢慢恶化还是瞬间崩溃。1.2 凌晨出事的三个特殊原因白天出事大家反应快因为人都在线、业务流量能帮忙验证问题往往在几十分钟内被发现。凌晨出事的诡异之处在于三点第一值班人员少。很多团队监控群里有七八个人但真正看手机的可能就一个。一旦这个判断失误恢复时间就是小时级。第二凌晨正好是批量任务的集中时段。几乎所有系统都喜欢把定时任务排在这个时间窗口但批量任务之间是否有资源竞争、是否会对在线业务造成锁竞争平时很少有人做压测。第三出事那会儿你的身体不在状态。半夜被叫醒认知能力下降是客观存在的一上来就手忙脚乱看日志、反复试命令只会浪费时间。有准备和没准备差的不是技术是预案。1.3 接到告警后的第一件事不是修而是定级我见过太多同事一听到告警就冲去翻日志结果半小时过去了还没判断出影响范围。正确做法是先花三分钟回答三个问题影响哪些接口、影响多少流量、有没有用户已经不可用。如果只是某个非核心报表接口慢级别可以降一降先观察。但如果是订单接口这类核心链路出问题直接按最高级别处理第一时间在值班群同步事故开始时间、影响范围、初步定位方向、我已经在做什么。这样做的好处是万一你自己搞不定别人看到消息就能立刻介入而不是等你扛不住了才开口求助。2. 前15分钟用一套固定的应急流程避免瞎忙2.1 三件套系统状态、应用日志、数据库状态我处理线上事故的基础顺序是固定的不绕弯先看机器再看应用最后看数据库。这个顺序的含义是从风险最小的排查开始逐步逼近最可能出问题的地方。第一要检查的是机器层面。登录应用服务器跑一遍常规命令看整体健康度# 查看系统负载和运行时间 uptime # 动态查看进程资源占用 top -c # 查看内存 free -h # 查看磁盘 df -h那晚的第一步就让我心里一沉。8核16G的机器load average已经飙到25以上top里MySQL进程的CPU占用超过300%问题大概率在数据库侧。但先别急着扑向数据库应用日志会告诉你具体是哪个环节卡住了。查看应用日志的命令很简单核心是抓取错误和超时信息# 查看最近30分钟的错误日志 grep -i error\|timeout\|panic app.log | tail -n 100 # 按时间过滤某个时间段的日志 sed -n /2024-01-01 02:0/,/2024-01-01 02:1/p app.log | tail -n 50日志里出现的关键词让我确认了方向大量获取数据库连接超时以及等待连接池线程。应用本身没崩所有请求都在排队等一个数据库连接。这就说明不是应用的问题而是数据库把连接占满了或者长时间持锁阻塞了其它事务。2.2 启动保险机制让业务流量喘口气事故处理不是只靠一条路径。在定位问题的同时如果发现核心接口已经大面积失败就要考虑要不要先把异常流量挡掉一部分保护剩下的系统不被拖垮。方案有好几种网关限流、摘除问题节点、开启降级开关。那晚我第一时间做了网关限流配置把订单查询接口的并发数临时限制到正常水平的50%。这么做不是目的而是给自己争取时间——让仍然有问题的数据库不再被更多请求反复冲击避免情况进一步恶化。这里有一个非常关键的教训不要在事故处理期间反复试错修改配置。每改一次网关、重启一个节点都要记录时间和结果。否则等事故结束后复盘你可能完全说不清到底是哪一步救回了系统。2.3 应急预案的价值先有手册再谈处理那晚我能保持还算稳定的节奏很大程度归功于团队平时维护的一份值班手册。手册里记录了常规排查命令、各系统负责人、历史事故复盘链接、常见故障的处理步骤。深夜头脑不清醒的时候直接照着手册来比自己临时想命令靠谱得多。如果看到这里你发现自己所在团队还没有这样的手册强烈建议花半天时间整理一份。最小内容包括系统架构图、各个服务的日志路径、数据库连接信息、常见告警对应的处理步骤。格式无所谓关键是内容能让人在凌晨三点照着操作。3. 深挖数据库从一串进程到真正的根因3.1 连接池为什么会被榨干确认问题在数据库侧之后我立刻连上MySQL实例先看当前活跃会话。这里用到一条非常基础但极其实用的SQL-- 查看所有非空闲会话按执行时间倒序 SELECT id, user, host, db, command, time, state, info FROM information_schema.PROCESSLIST WHERE command ! Sleep ORDER BY time DESC;执行结果触目惊心。几百条记录中有大量来自订单服务的连接卡在Waiting for table metadata lock和Updating状态最长的已经持续了二十多分钟。再配合查看长事务-- 查看当前所有事务 SELECT trx_id, trx_state, trx_started, trx_rows_locked, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started ASC;瞬间就清楚了。最老的那个事务从01:59开始已经锁定了大量行一直没提交。它在更新订单表而订单服务的正常读写都需要访问同一批数据。为什么连接池会被榨干这是一个连锁反应一个长时间不提交的事务占用了一个数据库连接同时它锁住的几千行数据让其它UPDATE和SELECT都阻塞等待。应用端的连接池有最大连接数比如30个。当30个连接全部被等待锁占住第31个请求就只能等在池子外面直到超时。这时候你去看应用日志全都是获取连接超时而不是数据库报错。3.2 元凶浮现凌晨2点的批量任务锁住数据的老事务是谁发起的通过会话信息和应用trace锁定到一台内网定时任务服务器。上面挂着一个cron任务每天凌晨2点执行历史订单对账结算。看一眼任务的SQL心里基本就有数了-- 问题SQL 简化示意全表扫描更新上千万行订单数据 UPDATE order_info SET settle_status done, settle_time NOW() WHERE create_date 2024-01-01 AND settle_status pending;这条SQL有两个致命设计缺陷。第一WHERE条件里的两个字段都没有合适的索引尤其settle_status pending过滤性很低数据库只能走全表扫描逐行判断。第二一个Update操作在InnoDB里会持锁直到事务提交全表扫描加锁锁的数量会随扫描范围膨胀到几万甚至几十万行和业务的正常写操作产生大量冲突。正常情况下这个任务跑几分钟就结束凌晨又是业务低谷影响不大。但那晚订单表的数据量涨到了历史最高全表扫描的时间拉长锁的范围也在扩大。业务侧虽然流量低但仍有持久重试的请求两边一叠加就形成了一场锁等待风暴。3.3 一次事故三类隐患回头看这次事故其实是三个问题叠加的结果批量任务调度没有任何保护机制。任务本身没有设置锁超时也没有看门狗来检测执行时间超过预期。它就该跑五分钟跑了三十分钟都没人报警这本身就是监控缺失。SQL写法没有考虑数据增长。半年前可能只扫几十万行半年后就变成上千万行但没有人在例行性能评估中注意到这个任务的执行计划发生了变化。监控指标太单一。团队只监控了接口成功率和响应时间没有监控数据库的长事务、锁等待、连接池使用率。如果这些指标单独配置了阈值告警大概率能在接口还没崩溃前就发现异常处理窗口会宽裕得多。4. 救火实操先杀会话再堵漏洞4.1 最快恢复手感的三个动作确认根因后我的动作顺序是杀会话、观察恢复、摘除隐患任务。杀掉长时间的异常会话是让数据库快速松一口气的最直接手段-- 先从 PROCESSLIST 查出异常会话的 id确认是定时任务发起的 -- 再执行 kill注意别把业务会话误杀了 KILL 15234;有人可能会问为什么不是重启数据库那是因为重启MySQL虽然也能让连接和锁全部释放但代价是重启过程本身有停机时间且InnoDB的崩溃恢复在数据量大的情况下可能耗时很久。相比之下KILL一个会话是秒级的锁会被立刻释放其它等待的事务自动继续执行影响面最小。杀完会话后我盯着监控面板等了十几秒。数据库的CPU先降下来了然后连接池的占用曲线开始回落接口成功率从80%恢复到95%以上。整个过程大概三分钟后面的流量慢慢消化掉积压的数据全部指标回归正常。第二步是立刻去冻结那个定时任务的后续触发。找到cron配置临时注释掉这个任务防止它第二天凌晨再跑一遍又把系统打崩。我没有选择当场直接给订单表加索引原因是晚上虽然流量低但大表DDL本身也有风险执行计划也没准备好贸然操作可能引发新的锁问题。正确做法是先在代码和SQL层面把隐患修掉再挑一个时间窗口做在线加索引。4.2 修复代码不只补索引还要断掉锁的后路第二天白天做的事才算真正把问题关上。第一件事给订单表的相关字段补上组合索引。这个操作直接消除全表扫描-- 为常用过滤条件添加组合索引 ALTER TABLE order_info ADD INDEX idx_create_date_settle_status (create_date, settle_status);注意索引字段顺序先放create_date是因为范围条件后放settle_status是因为等值条件。虽然这个SQL加了索引之后这个UPDATE还是会对扫描到的行加锁但扫描行数从千万级降到几千行持锁时间缩短到秒级和业务请求冲突的概率就微乎其微了。第二件事改造定时任务本身。我把一次性更新全量数据的逻辑改成按批次分片处理每次只处理5000行循环执行并且每批之间sleep几百毫秒。同时给任务加上一个全局互斥锁——用一张任务锁表记录当前是否有任务在运行避免数据量翻倍后多个任务重叠执行。改造后的核心逻辑从一把梭全表更新变成小步快走即使某批执行异常也不会影响整个数据库。4.3 参数调优给数据库留一道安全网修复代码之外我还调整了两个数据库和应用侧的参数目的是让类似的异常不再那么容易扩散。第一个是事务锁等待时间。InnoDB默认的锁等待超时是50秒对短事务来说够用但长事务会把这个时间拖得很长。我把核心交易库的锁等待超时适当调短并针对批量任务单独设置事务超时确保它不会默默跑半小时不提交-- 设置会话级锁等待超时不要全局一股脑改 SET SESSION innodb_lock_wait_timeout 15;第二个是应用连接池的空闲回收和获取超时参数。很多故障中连接池被占满一部分原因是慢接口把连接拿住不放。合理配置最大等待时间让应用在获取连接失败时能快速失败返回而不是无限排队可以避免把故障扩散到所有接口。这些调优不是越多越好。每改一个参数都要明白它会影响什么锁等待时间太短可能让正常业务在高并发时误报锁超时连接池太小又可能在流量尖峰时成为瓶颈。稳妥做法是先在测试环境压测验证再上生产。5. 天亮之后的复盘事故报告的核心三件事5.1 时间线比结论更有价值事故处理完不代表工作结束。我习惯在当天就写出复盘因为记忆和聊天记录还在隔一天很多细节就模糊了。复盘报告的第一部分必须是时间线。不是自夸式的X点开始处理而是每一条记录都要标清楚什么时间点、观察到了什么现象、做出了什么操作、操作产生了什么效果。这条时间线既是给团队看的也是留给自己下次检查的原始数据。时间线整理得越细复盘时越容易发现两个问题哪些时间点是浪费掉的哪些动作是无效的。比如那晚我发现从确认数据库有问题到找到长事务中间花了将近十分钟。原因是我先花时间翻了一遍应用日志再连数据库的时候前两条排查SQL都不够聚焦。这个分钟级别的时间浪费只有复盘才能暴露。5.2 五个为什么揪出流程漏洞我写复盘时会强制自己问一组问题为什么发生、为什么没有提前发现、为什么在凌晨爆发、为什么恢复耗时这么久、以后怎么避免同类问题。这套追问最终会指向流程层面的漏洞而不是某一个人的失误。那次事故的答案依次是批量任务SQL缺少索引保护监控面板没有覆盖数据库长事务指标任务执行时间没有超时控制值班手册里没有针对锁等待的处理步骤。每一层答案都应该对应一个具体行动项。比如为什么没有提前发现对应的行动项是增加数据库锁等待、长事务、连接池占用的独立告警设置比业务告警更敏感的阈值。行动项必须有人认领、有截止日期否则复盘就只是走个形式。5.3 告警噪音也是隐患别讳疾忌医复盘过程中团队有人提出为什么当时没有更早收到数据库的告警答案很简单因为监控系统只配了几条业务层面的告警数据库侧的指标没有告警规则。不少团队有这种倾向——数据库告警规则配了很多结果天天被误报打扰最后大家直接忽略告警变成狼来了。这是一个反面信号。告警宁缺毋滥不假但核心指标绝对不能漏。我的做法是告警分级红色级别只保留真正会引发业务不可用的事件黄色级别是潜在隐患单独群推送不打扰值班手机。数据库的长事务、锁等待、慢查询数量都属于值得关注的黄色级别出现异常时先记录持续超过阈值再升级。6. 凌晨事故避坑清单每一分钟都花在刀刃上6.1 三类常见凌晨事故速查表这次事故属于批量任务引发的锁风暴。凌晨出问题翻来覆去也就那几类。我整理了一份自己的速查表遇到对应症状可以直接按步骤操作典型症状常见根因应急处理长期方案接口超时日志大量获取连接超时定时任务长事务锁表连接池耗尽查PROCESSLISTKILL异常会话重写批量SQL分批限流加索引锁超时限制CPU飙高慢查询变多凌晨报表/统计任务全表扫描定位慢SQL临时限流该任务优化SQL物化视图计划任务分流磁盘空间突然告警日志归档或临时表生成过快清理过期日志停相关任务日志轮转策略磁盘监控上限应用节点假死重启后恢复内存泄漏或连接句柄耗尽滚动重启节点摘除故障节点压测找泄漏点增加资源监控这张表不追求大而全核心价值是逼着自己在平时就把每一类故障的第一反应写下来。真正出问题时你不需要思考照着表做就行。6.2 不要迷信监控全覆盖出事之后很多团队会陷入一个误区疯狂加告警恨不得把所有指标都配上规则。我对此的态度是克制。告警的价值在于让人在正确的时间被提醒而不是制造噪音。那晚如果是数据库长事务告警先响我可能提前二十分钟介入。但如果我同时接收几十条无关告警真出事时反而可能被刷屏淹没关键信息。所以告警配置的原则是业务核心链路优先数据库关键指标其次级别分明推送渠道分开。6.3 培养自己的肌肉记忆最后说一个我自己体会很深的事。处理事故的能力不是靠读文章读出来的是靠一次次真实或模拟的故障练出来的。团队可以定期搞故障演练。比如故意在测试环境跑一个有问题的批量任务让值班的人按照手册去处理。我自己就是在一个很偶然的机会中第一次KILL了一个死锁事务从那以后遇到锁等待的问题心态明显不一样。演练的目的不是让人记住命令而是让人在心跳加速的情况下仍然能保持清晰的步骤意识。还有一个小习惯把常用的排查SQL、常用命令、历史事故链接都放在一个私人的速查笔记里每周值班前翻一遍。上线系统多的人不可能把每个细节都装进脑子但有了这套笔记半夜醒来的你也能快速进入状态。写到这儿那次凌晨2点的惊魂时刻就算完整复盘完了。我个人最大的体会是生产事故永远有突发的一面但它的恢复过程其实是预案、工具和个人习惯共同作用的结果。那次之后我再也没有在夜里被同一个问题吵醒过——不是因为运气变好了而是因为这套流程把隐患堵在了爆发之前。也希望读到这里的你下次遇到凌晨的告警能多一份从容而不是又多一次手忙脚乱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑