CPU只有30%,磁盘却被打满:一批批处理引发的超时

简介: 对账批处理把磁盘打满,CPU 只有 30%,慢日志里没有慢 SQL。拆清 IO 三类来源:物理读、刷脏、fsync,给出 iostat 到 checkpoint 的定位顺序。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

上个月底跑对账,批处理一开,接口开始超时。CPU 只有 30%,慢查询日志里一条慢 SQL 都没有。我盯着 SQL 看了半小时,最后在 iostat 里看到 w/s 飙高,util 贴着 100%。业务不是被 SQL 拖慢的,是被磁盘排队拖慢的。

一、先别盯 SQL,先看 IO 是谁产生的

IO 高,第一反应通常是 SQL 扫得太多。这个方向只对了一半。数据库的 IO 至少有三类来源。第一类是前台读,查询要的页不在 buffer pool 里,只能去磁盘拿。第二类是后台写,脏页要刷盘,checkpoint 要推进。第三类是日志写,redo 和 binlog 每次提交都要落盘。

三类 IO 的解法完全不同。前台读多,去查索引和缓存。后台写多,去查刷脏和 redo 容量。日志写多,去查提交频率和同步策略。方向错了,调哪个参数都像在碰运气。先分类型,再谈优化,顺序不能反。

来源 典型指标 先查什么
前台读 物理读比例、Innodb_buffer_pool_reads 索引、buffer pool 容量、冷数据扫描
后台刷脏 Innodb_buffer_pool_pages_dirty、Innodb_data_pending_writes redo 占用、innodb_io_capacity、盘的上限
日志写 Innodb_os_log_pending_writes、Innodb_data_fsyncs 提交频率、组提交、sync_binlog

顺带一个常被忽略的点。慢日志只记单条语句的耗时。批量任务里每条都快,慢日志当然干净。但磁盘上的排队,发生在语句之间和后台刷盘里。所以慢日志没有慢 SQL,不能证明盘没压力。

二、设备层:先看等待和队列

设备层是我最后才想到的地方,其实是第一步。先看 iostat -x 1。r/s 和 w/s 告诉你读写比例,r_await 和 w_await 告诉你哪一边在等。aqu-sz 是平均队列长度。队列越长,说明请求在排队,不是设备没干活。

%util 要小心。它只说明设备有没有活干,不说明排队有多长。在 NVMe 和云盘上,这个数还不一定准。util 100% 但 aqu-sz 很低,可能是设备本身慢。util 不高但 await 很高,可能是上层在排队。真正要盯的是 await 和 aqu-sz,再加上云盘控制台里的 IOPS 和吞吐曲线。

top 里的 wa 也别太信。新内核上,它统计的是 CPU 等 IO 的时间片。设备很快、队列很短时,wa 也可能不高。vmstat 1 里的 b 列更实在,它数的是卡在不可中断睡眠的进程。这个数一直大于核数,说明有东西在等盘。

iostat -x 1
pidstat -d 1

pidstat -d 能告诉你是不是别的进程在抢盘。备份工具、日志采集、大批量导入,都会出现在这里。如果 mysqld 的读写量不大,盘却被别人占着,那问题不在数据库里。先把这个排除掉,再往下查。

三、 再看 MySQL 内部,读写各占多少

设备层只能告诉你忙,不能告诉你谁在忙。performance_schema.file_summary_by_event_name 按事件统计读写次数、耗时和字节数。COUNT_READ 和 COUNT_WRITE 看次数,SUM_TIMER_READ 和 SUM_TIMER_WRITE 看时间,SUM_NUMBER_OF_BYTES_READ/WRITE 看体量。哪个大,哪一类就是主因。

file_summary_by_instance 再往下钻一层,能看到具体文件。ib_logfile 或 #innodb_redo 忙,多半是日志。#ib_16384_0.dblwr 忙,多半是双写。某个 *.ibd 忙,多半是这张表的读写。binlog 忙,多半是写入量大或者从库在追。文件名比指标更直接,一眼就能把方向缩小。

-- 1. 按事件看读写各占多少
SELECT EVENT_NAME,
       COUNT_READ, SUM_TIMER_READ,
       COUNT_WRITE, SUM_TIMER_WRITE,
       SUM_NUMBER_OF_BYTES_READ,
       SUM_NUMBER_OF_BYTES_WRITE
FROM performance_schema.file_summary_by_event_name
WHERE EVENT_NAME LIKE 'wait/io/file/%'
ORDER BY SUM_TIMER_WRITE DESC
LIMIT 10;

-- 2. 按文件看谁最忙
SELECT FILE_NAME, COUNT_READ, COUNT_WRITE,
       SUM_NUMBER_OF_BYTES_READ,
       SUM_NUMBER_OF_BYTES_WRITE
FROM performance_schema.file_summary_by_instance
ORDER BY SUM_NUMBER_OF_BYTES_WRITE DESC
LIMIT 10;

Innodb_buffer_pool_reads 除以 Innodb_buffer_pool_read_requests,就是物理读比例。这个数抬头,说明 buffer pool 不够,或者查询在扫冷数据。它和刷脏是两条线,别混在一起看。一个看读,一个看写,方向不同。

还有一个数容易被漏掉,Innodb_buffer_pool_wait_free。它数的是等空闲页的次数。这个值在涨,说明 buffer pool 里连干净页都不够用了。刷脏跟不上,查询只能停下来等页。命中率低和等页多,常常一起出现。根因却不同,一个在读,一个在写回速度。

四、 刷脏为什么会突然变猛

我一开始以为刷脏是原因,后来发现它也是被逼的。如果写 IO 占大头,先看脏页。Innodb_buffer_pool_pages_dirty 是当前脏页数,Innodb_buffer_pool_pages_free 是空闲页数。脏页逼近 innodb_max_dirty_pages_pct,InnoDB 就会加速刷盘。8.0 社区版默认阈值是 90%,低水位是 10%(对应 innodb_max_dirty_pages_pct_lwm)。刷脏是渐进的,不是等到 90% 才动手。云上 RDS 版本可能会调整这两个默认值,动手前先确认实际配置,别拿社区版的数直接套。

真正把刷脏逼急的,通常是 redo。redo 是环形的,checkpoint 追不上。InnoDB 就得把脏页刷下去,才能复用日志空间。redo 容量越小,这个循环越紧。8.0.30 之后可以用 innodb_redo_log_capacity 调大,默认 100MB。调大能给 checkpoint 更多余量,代价是崩溃恢复变慢。

innodb_io_capacity 也要看。它告诉 InnoDB 后台任务能用多少 IOPS,默认 200。盘还有余量,适度调高能帮刷脏追上。盘已经满了,调高只会让队列更长。云盘还要看 IOPS 和吞吐上限。突发额度用完,表现会突然变差。

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_free';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_wait_free';
SHOW GLOBAL STATUS LIKE 'Innodb_data_pending_writes';
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_pending_writes';
SHOW GLOBAL STATUS LIKE 'Innodb_data_fsyncs';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';

Innodb_data_pending_writes 和 Innodb_os_log_pending_writes 更直接。前者是等着写的数据页,后者是等着写的日志。两个数一直下不去,说明盘已经跟不上,不是 InnoDB 不想刷。这时候再调大 io_capacity,只会把队列拉得更长。

redo 用到哪了,SHOW ENGINE INNODB STATUS 里能算。Log sequence number 减 Last checkpoint at,就是还没被 checkpoint 覆盖的字节数。再除以 innodb_redo_log_capacity,就是当前占用比例。这个比例长期贴着上限,刷脏就是被 redo 逼出来的。占比不高却还在猛刷,方向要往脏页比例和 buffer pool 压力上找。

日志这条线,先算 fsync 的频率。每次提交都要 redo 落盘,innodb_flush_log_at_trx_commit=1 时一次提交一次 fsync。QPS 上万,fsync 就是上万次。Innodb_data_fsyncs 隔几秒取一次差值,就能看出这个量级。组提交能合并一部分,但压不住持续的提交频率。

五、那天到底发生了什么

回到那次对账。批处理一次更新两百万行,分成很多小批。每批 SQL 都很快,所以慢日志里什么都没有。但每批都在写 redo 和 binlog,redo 很快逼近 checkpoint 的边界。InnoDB 开始加速刷脏,磁盘的写队列一下拉长。

接口的查询本来命中 buffer pool,这时脏页和新读的页一起抢空间。命中率掉下来,物理读跟着上来,读和写挤在同一条盘上。CPU 看着不高,是因为时间都花在等 IO 上,不在算。iostat 里的 w_await 和 aqu-sz,比 %util 更能说明问题。

六、止血和根治

止血先做减法。批处理降速,每批之间留间隔。备份错峰,别和业务抢盘。读流量切到从库,先把前台保住。云盘如果还有余量,临时扩 IOPS 是最快的。如果已经到上限,扩了也没用,只能等负载下去。

参数要谨慎。redo 容量可以调大,给 checkpoint 留余量。innodb_io_capacity 只在盘还有余量时调高。innodb_flush_neighbors 在 SSD 上可以关掉,减少无谓的邻页刷写。innodb_flush_log_at_trx_commit 和 sync_binlog 别为了性能随手改。那是拿持久性换吞吐。

根治还是回到结构和习惯。大批量更新拆小、限速、错峰。历史数据归档,别让一张表无限长。备份放到独立盘或从库。buffer pool 按内存给足,别让热数据反复淘汰。IO 是结果,不是原因。原因在写入节奏和访问模式里。

七、 避坑清单

IO 高先看 iostat -x,别只看 %util。%util 只说明设备有没有活干,await 和 aqu-sz 才说明有没有排队。NVMe 和云盘上,%util 100% 不代表已经到极限。先看队列,再下结论。

别把 IO 高都算在慢 SQL 头上。先分前台读、后台刷脏、日志 fsync,再看是哪一类。慢日志里没有慢 SQL,不代表没有 IO 压力。方向错了,加索引也救不了刷脏。

最后一条是我自己踩的。有次看到 IO 打满,我把 innodb_io_capacity 从 200 调到 5000。我想着多给点能力,刷脏就能追上。结果刷脏更猛,队列更长,业务更卡。后来才知道,盘的上限就在那里。调参数只是让请求排得更长。现在我先看 w_await 和云盘 IOPS 上限,再决定动不动参数。

写在最后

IO 排查最容易走偏的地方,是只盯 SQL。SQL 慢当然会带来 IO,但 IO 高不一定是 SQL 慢。刷脏、日志、备份、云盘额度,任何一个都能把盘打满。你看到的是一根曲线,背后可能是四五个来源。

先分类型,再看文件,最后才动参数。这个顺序能让你少调很多没用的开关。磁盘不会说话,但 await、aqu-sz 和文件名会。读懂它们,比记住一堆参数有用。

你们遇到过 CPU 不高、磁盘却打满的情况吗?最后查到是刷脏、备份还是慢 SQL?当时是怎么定位的?评论区聊聊。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
|
5天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
2117 13
|
云安全 安全 网络安全
|
5天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
919 1
|
18天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
3068 49
|
11天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2108 2
|
5天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
|
10天前
|
人工智能 JSON Linux
【保姆级】ComfyUI部署教程(下载+安装+配置+工作流,2026最新)
ComfyUI 是一款开源节点式AI生成工具,支持图像、视频、音频及3D内容创作。通过拖拽连线组合模型加载、提示词、采样等节点,构建可复用、易修改的工作流,兼容SD1.5、SDXL、Flux、Qwen Image等主流模型。
|
4天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)