ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

数据库磁盘MBPS飙高故障复盘:从误判慢SQL到全站恢复的排查实战

数据库磁盘MBPS飙高故障复盘:从误判慢SQL到全站恢复的排查实战 那天下午的故障我到现在还记得监控大屏弹出来时的压迫感。业务方反馈说页面打不开、接口大量超时客服群里的截图一张接一张紧接着数据库服务器的磁盘监控曲线直接贴满天花板——MBPS每秒读写吞吐量长时间处于高位几乎一直在上限附近震荡。数据库服务器的磁盘MBPS高这个指标平时很少单独引起我的警惕因为磁盘有吞吐余量是很正常的事但这一次它直接演变成了一次全站级别的故障。这篇文章的核心就是对这次故障的完整复盘从监控告警、误判方向、一步步定位到根因、执行止血、恢复验证再到后续的监控和流程改进。如果你也在维护数据库服务器或者平时会跟磁盘IO指标打交道这篇内容应该能帮你省掉不少弯路。哪怕你是刚入行的运维或后端把MBPS和IOPS、await这些概念搞明白对排查线上故障也有很大帮助。1. 故障发生的前半小时现象、误判和第一次定位偏航1.1 监控告警先于用户投诉到达但第一时间没有人重视先说时间线。当天下午14:37监控系统开始推送数据库服务器告警告警内容很常规CPU使用率从10%左右突然跳到70%以上活跃连接数持续上涨主库平均响应时延从5ms恶化到800ms。看到这类告警我的第一反应是“又有慢SQL了”。因为数据库出现性能问题90%的场景最后都指向SQL所以一开始我根本没往磁盘层面想。14:42客服群开始有用户反馈App部分页面加载不出来。这时候我登录服务器看了一眼top发现CPU并不算真正的繁忙但waIO Wait数值高得吓人基本在60%以上。这说明大量的CPU时间都花在了等待IO完成上应用没法继续推进。再用iostat -x 1看磁盘emmmutil已经接近100%写吞吐MBPS持续贴着峰值。从这一刻起问题才真正被我重视起来——不是慢SQL导致的逻辑问题而是物理IO层面已经接近饱和。1.2 为什么我一开始把排查方向押在了“慢SQL”上这个误判很典型值得展开说说。当时慢查询日志里确实出现了一批平时也见过的查询它们的执行时间从几十毫秒恶化到了几秒。团队里有人开始分析这些SQL尝试加索引、改执行计划。但折腾了十几分钟发现一个问题这些SQL在业务低峰期也是这个执行计划Explain结果没有明显异常优化空间非常有限。真正让我意识到方向偏了的是活跃会话列表里的一个现象大量会话不是卡在“Sending data”这种查询执行状态而是卡在“Waiting for table flush”和“statistics”这种与元数据锁、磁盘刷盘相关的状态。这说明问题不在SQL本身而在于底层IO让所有需要读数据、刷日志的操作都排起了长队。这里我想先说一个观点数据库服务器故障表面上症状最多的是SQL慢但根因经常在更底层的地方。如果只盯着SQL优化很容易在错误的森林里打转。2. 把“MBPS高”从模糊指标还原成可定位的现场2.1 MBPS、IOPS、util、await这几个磁盘指标必须分开理解很多人一看到“磁盘忙”就以为只是“占用率100%”但磁盘指标背后藏着完全不同的含义。我见过不少同事把MBPS和IOPS混为一谈导致判断出现偏差这里必须先把基础概念掰清楚。指标含义正常情况参考值以SSD为例危险信号MBPS每秒读写字节数即吞吐量视磁盘规格而定SSD普遍能达到数百MB/s到数GB/s长时间贴近上限IOPS每秒读写操作次数企业级SSD通常上万随机读写场景下显著下降util磁盘忙碌时间占比低于70%持续高于80%-90%awaitIO请求平均处理时间含排队时间低于20ms超过100ms说明排队严重svctm实际IO服务时间不含排队接近await是正常远小于await说明大量请求在排队这里有个关键点MBPS高本身不一定致命但如果MBPS高同时伴随util接近100%、await飙升那就说明磁盘真的处理不过来了。MBPS高只代表“数据量大”await高才代表“请求在等”等多了就形成长尾最终拖垮所有依赖磁盘的会话。这次故障里磁盘MBPS高得离谱同时w_await一度超过500msqueue深度请求队列数也在持续累积。这已经超出了正常业务波动的范畴可以确定是某个进程在进行密集的写盘操作。2.2 用 iostat、pidstat、show processlist 组合定位写盘源头既然怀疑有进程在疯狂写盘下一步就是找出它。我按三步来操作这几步对所有类似问题都通用第一步确认磁盘是否真的忙忙在哪块盘。# 每1秒输出一次磁盘扩展信息 iostat -x 1重点看%util、r_await、w_await、aqu-sz平均队列深度。当时输出显示写等待时间是读等待的几倍基本可以锁定是写IO导致的问题。第二步按进程维度定位写盘来源。# 每1秒统计进程的IO读写速率 pidstat -d 1pidstat输出里wrkb/s列就是各进程每秒写入的数据量。正常的数据库实例写个几十MB/s很常见但当时MySQL主进程的写吞吐一下冲到几百MB/s这明显不正常。第三步进MySQL内部看会话和事务状态。SHOW FULL PROCESSLIST; SELECT * FROM information_schema.innodb_trx\G这一步是很多人的盲区。磁盘指标只能告诉你“谁在操作系统层面写”但数据库内部是哪个连接发起的大事务必须通过MySQL视图来看。当时我查到有两个连接特别显眼一个是执行了大半天还没结束的备份任务mysqldump另一个是一条大表的ALTER TABLE变更。这两个操作同时落在同一块盘上直接引爆了IO瓶颈。3. 根因复盘三个写源撞进同一个时间窗口谁都没做限流3.1 事件还原备份任务、同步组件、大表DDL的叠加效应根因不是单一的一个坏动作而是三件事撞在了同一个时间段互相放大。第一件事是备份任务。我们的全量备份使用mysqldump每天凌晨2点开始跑但核心订单库数据量已经涨到了800GB左右备份速度越来越慢下午14:00时这个任务已经持续了12个小时还没结束。也就是说在我们处理故障的时候备份任务已经默默在磁盘上读写了接近一整天。第二件事是数据库同步组件。业务里有一套基于binlog解析的同步工具类似Canal/DataX那类方案用于将主库数据实时同步到下游。当天下午因为网络抖动同步组件断连后自动重连重连时它会尝试追平断开的binlog位点消费速度被拉满进一步增加了磁盘读取压力。第三件事是压垮骆驼的最后一根稻草业务方临时发起了一条ALTER TABLE给一张千万级的订单明细表加索引。在MySQL 5.7的默认配置下这类表结构变更并不是单纯修改元数据而是会在磁盘上重建整张表——创建临时文件、逐行拷贝数据、最后再替换原表。这个“写放大”过程非常夸张一张50GB的大表变更期间可能产生数倍于自身大小的临时写入。这三件事叠加在一起磁盘MBPS被直线拉满。原本任何一个单独出现都不会立刻导致全站故障因为它们都有自己的空闲窗口和低谷期。但偏偏在同一个下午凑齐了谁也没有为谁让路。3.2 为什么一个“IO高”能演化成全站不可用很多人会好奇磁盘IO高最多是数据库慢一点怎么会全站打不开这里有一个典型的故障放大链路磁盘写吞吐饱和 - redo log、binlog刷盘变慢 - 事务提交被阻塞 - 连接迟迟不能释放 - 应用侧连接池被打满 - 后续请求排队 - 调用方等待超时并触发重试 - 重试流量进一步加剧数据库压力 - 数据库彻底失去响应。我们当时看到的表象是“全站接口超时”但其实链路中间已经经历了好几层恶化。尤其是连接池打满之后即使数据本身没有问题应用也无法从连接池里拿到一个空闲连接去执行SQL于是所有业务全部卡住。另外还有一个容易忽略的点磁盘MBPS高会导致buffer pool刷新脏页的速度跟不上写入速度InnoDB的后台线程会反过来限制新的写入操作形成恶性循环。这就是为什么IO型故障的“止血窗口期”比CPU型故障更短——CPU高还有调度优先级可以抢救磁盘满了所有线程都只能干等。4. 止血操作实战先断写、再降IO、最后恢复读4.1 停机式操作的优先级怎么排现场处理这类问题最忌讳一上来就重启数据库。重启意味着缓存清空、连接全部断开恢复时间反而更长。我的思路是先确定哪些任务可以中断且损失最小按损失从小到大排序逐个动手。当时的执行顺序是这样的用SHOW FULL PROCESSLIST找到两个关键会话mysqldump备份会话以及ALTER TABLE会话先kill备份会话。理由很简单备份失败可以重新调度对业务没有直接影响而ALTER TABLE如果中途kill虽然会回滚但已经产生的临时文件写入会需要一点时间释放再kill ALTER TABLE会话。这里要注意kill一个DDL操作不是瞬间完成的它需要等待当前拷贝批次结束后才能安全回滚所以命令发出后要持续用iostat观察MBPS并不会立刻掉下来第三步是把binlog同步组件暂停掉不让它继续追位点。同步组件有断点续传机制停一会儿不会丢失数据只是下游会有延迟每完成一个操作等30秒左右观察磁盘指标再决定下一步。这个顺序的本质是先切断能承受得起重来的写任务再处理代价高的写任务最后再对实时链路做限制。不建议一上来就停主库的写入因为对业务影响最大。4.2 恢复之后别急着宣布“故障结束”磁盘MBPS降下来、连接数回落、接口超时消失并不是终点。IO故障往往伴随一系列次生问题必须逐一验证。我当时列了一个检查清单这个清单现在也一直保留在团队的故障恢复SOP里检查项验证方式预期结果主从同步状态SHOW SLAVE STATUS\GSeconds_Behind_Master逐步归零binlog同步位点同步工具管理后台消费位点正常推进无积压临时表空间文件检查 ibtmp1 文件大小kill DDL后临时空间自动回收redo log写入状态监控InnoDB redo log相关指标写速率恢复正常范围业务成功率网关或接入层看请求成功率恢复到故障前水位连接池情况应用连接池活跃/空闲数没有满连接等待线程清零那天恢复后ktor那批因为重试而堆积的请求在几十秒内消化完页面恢复可访问。但主从延迟整整持续了几分钟才追上这期间从库仍处于落后状态。所以恢复验证阶段不能只盯着主库的指标从库和下游同样要盯住。5. 故障之后监控阈值、任务调度与大表DDL的改进5.1 监控不能只报“MBPS高”要组合判断一次故障下来最值钱的产出就是改进监控规则。过去我们的磁盘告警非常粗糙——MBPS超过某个固定值就报警导致经常误报大家慢慢就不看了。经过这次复盘我把磁盘监控调整成了“组合告警”。组合告警的含义是同时满足多个条件才会触发。例如磁盘util持续5分钟大于80%且await持续5分钟大于100ms且当前队列深度aqu-sz大于16或MBPS超过磁盘理论性能的80%并持续时间超过10分钟。如果只是MBPS瞬时飙一下不报警只有多个指标同时恶化说明真的存在IO瓶颈才触发通知。这样既减少了噪音也保证真正出问题时一定有人响应。如果你用的也是Prometheus Grafana体系可以参考下面的查询表达式# 磁盘写吞吐速率换算为MB/s rate(node_disk_write_bytes_total{instancedb-server-01}[5m]) / 1024 / 1024 # 磁盘IO使用率近似值 100 - (avg by(instance) (rate(node_disk_io_time_seconds_total{instancedb-server-01}[5m])) * 100) # 磁盘队列深度 node_disk_queue_depth{instancedb-server-01}只有在监控上多花功夫才能让你在下次故障时有更多时间做决策而不是临场猜。5.2 任务调度和变更流程让“时间窗口重叠”不再发生这次故障的直接诱因就是三个写IO任务重叠。所以后续我们在流程上做了几个硬性规定第一全量备份必须在业务低峰完成且要有超时熔断机制。如果备份超过预期时长比如超过8小时自动终止并触发告警而不是让它无限期跑下去潜伏成一颗定时炸弹。第二大表DDL不能直接在生产库上执行必须走改表工具。这里推荐pt-online-schema-change或gh-ost它们通过触发器或binlog同步的方式以chunk为单位分批复制数据能把瞬时的大规模写IO拆成持续的低频写IO不会一上来就打满磁盘。这次事故如果当时用的是这类工具MBPS大概率不会冲顶。第三同步组件必须加限流和延迟告警。binlog同步组件重连后追位点是一个很容易被忽视的IO爆发点要在组件层面配置消费速率上限避免追位点时把磁盘打满。我把日常常见的几类磁盘写任务整理成了下面这张调度表每次排任务前都会过一遍任务类型常见工具典型IO特征建议措施全量备份mysqldump、xtrabackup持续数小时的高吞吐严格低峰窗口超时熔断与DDL错开增量同步Canal、DataX、Flink CDC重连后可能出现追位点高峰客户端限流自动退避大表DDLALTER TABLE5.7突发性写放大使用 pt-osc/gh-ost业务低峰执行归档删除DELETE大范围数据大量随机写分批删除限制单批大小批量报表定时任务大查询引发临时文件写盘资源组/限流避免与高峰重叠6. 复盘之后我沉淀下来的几条运维经验写到最后分享几点经历过这次事故后我个人最真实的感受。第一单独的MBPS指标没有意义一定要组合看IOPS、await、队列深度和连接数。磁盘吞吐高不代表磁盘需要紧急处理但如果写等待时间在涨、连接数在堆积就必须当作最高优先级处理。出故障时真正有用的不是某一条告警而是多条告警之间的交叉印证的逻辑。第二出事后先做影响面收敛再谈根因定位。那次故障我们前二十分钟都在分析慢SQL其实处理IO故障的正确顺序是先确认哪些操作可以安全中断把磁盘负载降下来恢复服务然后再慢慢查是谁触发的。顺序反了故障时间就会被拉长。第三大表DDL永远不要“顺手做”。这次事故以后我们团队对DDL非常谨慎任何涉及大表的在线变更都要走审批、评估存储空间、预估IO开销并选择gmtools。看上去流程重了但比起一次全站故障这点成本实在微不足道。第四恢复之后还要再观察至少一小时。同步组件重连后的追位点往往是延迟出现的第二波IO高峰。我们那次就是在业务恢复后约四十分钟同步组件又追了十几分钟的位点虽然这次没有把磁盘打满但也足以让人捏一把汗。数据库服务器的磁盘MBPS高表面上看是一个资源指标告警背后其实是任务调度、变更管理和监控策略的综合问题。希望这次故障复盘能给你提供一条有价值的排查思路。
返回列表