前事不忘,后事之师,不忘国耻!

 用户注册  找回密码
 用户注册
搜索
查看: 5|回复: 0

数据库主机磁盘 IO 瓶颈排查实战:从 iostat 到 fio 压测

[复制链接]

数据库主机磁盘 IO 瓶颈排查实战:从 iostat 到 fio 压测

[复制链接]
dbaai

主题

0

回帖

301

积分

DBAAI

积分
301
3 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?用户注册

×
数据库主机磁盘 IO 瓶颈排查实战:从 iostat 到 fio 压测


一、具体的问题

上周三晚批处理窗口,业务方报批量导入比平时慢了近一倍。登上数据库主机一看:CPU 不高(us 12%,sy 6%),内存充裕,但 load average 4.8,vmstat 的 b 列长期挂着 3~4 个不可中断进程,wa 列动辄 25% 往上。应用端的表现是:单条 insert 平时 2ms,高峰期飙到 200ms 以上;MySQL 错误日志里每隔几分钟就有一条 "page cleaner" / checkpoint 落后的告警。

很多人第一反应是"加块盘"或者"换 NVMe"。但先把问题拆开看:到底是哪块盘在忙?是读慢还是写慢?是单次 IO 本身就慢(设备延迟高),还是请求排队太多(队列积压)?是业务流量涨了,还是某个后台任务(备份、归档、监控 agent)在抢 IO?不搞清楚这些就动手换硬件,多半是把钱花在了不疼的地方。

这台主机上一共三块盘:sda 放系统和监控,sdb 放 MySQL 数据文件,sdc 放 redo/binlog。批处理窗口变慢,嫌疑最大的自然是 sdb 和 sdc。下面按实际排查顺序走一遍。

二、核心原理

第一,iowait 高不等于磁盘慢。 wa 只表示"CPU 里有比例的时间在等 IO 完成",它是 IO 需求和 CPU 空闲的相对值。业务低峰期 CPU 几乎空闲,哪怕磁盘一般忙,wa 也可能显示 30%+;反过来业务高峰期 CPU 满载,同样多的 IO 等待在 wa 里的占比反而被摊薄。wa 只能当"有 IO 等待"的信号,不能当"磁盘慢"的结论。

第二,%util 在现代设备上严重失真。 %util 的原始定义是"采样周期内至少有一个 IO 在处理的时间占比"。对机械盘单队列时代,它近似于"盘有多忙";但对 NVMe 和 RAID 卡背后的多队列设备,盘可以同时处理几十上百个 IO,%util 到 100% 只说明"这一秒里始终有 IO 在飞",离饱和可能还远。看饱和度要看 aqu-sz(平均队列深度)和 await 的变化趋势,别盯着 %util 下结论。

第三,区分设备延迟和排队延迟。 iostat -x 里的 await 是"每个 IO 从进队列到完成的总时间"(含排队),它高可能是设备本身慢,也可能是排队多。内核 4.18 以后的 iostat 输出里 svctm 已废弃(算出来就是个推导值,不要再用),判断设备本身快慢更可靠的办法是:低负载时段压一个单深度的 fio(iodepth=1),那个数字才是设备的裸延迟。

第四,数据库的 IO 是"放大"过的。 应用写一行 200 字节,落盘路径上可能是:一个脏页(16KB)的刷写、一次 redo fsync、一次 binlog 写、主从再各同步一次。做容量和延迟预算时要以数据库视角算放大倍数,而不是看应用侧的写入量。这也是为什么 redo 盘和数据盘混放时,提交延迟(commit)会被刷脏页连累——fsync 在同一块盘上排队。

三、实例参考(动手步骤)

步骤 0:确认现状基线
  1. # 每秒采样一次,连采 10 次,看全局
  2. iostat -x 1 10
  3. # 重点关注:r/s w/s、rkB/s wkB/s、r_await w_await、aqu-sz、%util
  4. # 同步看 CPU 侧
  5. vmstat 1 10   # b 列=不可中断进程数,wa=IO 等待占比
复制代码

当晚的采样结果(批处理窗口):

设备w/swkB/sw_awaitaqu-sz%util
sdb(数据盘)8205240086.412.398.9
sdc(日志盘)310098004.10.822.4


数据盘 sdb 写延迟 86ms、队列深度 12,明显是瓶颈在 sdb;日志盘 sdc 健康。问题收敛到一半了。

步骤 1:定位是哪个进程在打 IO
  1. # 按进程看读写吞吐,采样 10 次
  2. pidstat -d 1 10
  3. # 或者交互式看(需要 iotop)
  4. iotop -oPa
复制代码

结果发现除了 mysqld(w=38MB/s),还有一个 arch_ckpt.sh 起的 tar 进程在读 sdb 写 sda,速率 25MB/s——是归档脚本和批处理撞了窗口。

步骤 2:从数据库侧看谁在刷
  1. -- MySQL 8.0:按文件看 IO 排名
  2. SELECT file_name, count_read, sum_timer_read/1e12 AS read_s,
  3.        count_write, sum_timer_write/1e12 AS write_s
  4. FROM performance_schema.file_summary_by_instance
  5. ORDER BY sum_timer_write DESC LIMIT 10;
  6. -- 看 checkpoint 压力
  7. SHOW GLOBAL STATUS LIKE 'Innodb_checkpoint%';
  8. SELECT VARIABLE_VALUE FROM global_status
  9. WHERE VARIABLE_NAME IN ('Innodb_buffer_pool_wait_free','Innodb_log_waits');
复制代码

Innodb_log_waits 期间涨了 4000 多,说明 redo 写入在等日志盘之外的资源——结合步骤 0,是数据盘拖累的脏页刷写跟不上。

步骤 3:测设备的裸延迟(低峰期做)
  1. # 单深度随机写,测裸延迟(务必对测试文件,别打业务数据文件!)
  2. fio --name=lat --filename=/data/fio_test --direct=1 --rw=randwrite \
  3.     --bs=4k --iodepth=1 --numjobs=1 --time_based --runtime=30 \
  4.     --size=2G --group_reporting
复制代码

sdb 单深度 4k 随机写延迟 0.9ms——设备本身没问题。结论修正:不是盘慢,是请求量叠加排队(820 写/秒 × 数据+日志混刷)。这直接改变了治理方向:不需要换盘,需要错峰和限流。

步骤 4:治理落地
  1. # 1) 归档脚本加 ionice + nice,并挪到凌晨 3 点执行
  2. # crontab 改为:
  3. # 0 3 * * * ionice -c3 nice -n19 /opt/scripts/arch_ckpt.sh
  4. ionice -c3 nice -n19 tar -czf /backup/db_1009.tgz /data/mysql/db1/
  5. # 2) 限流批处理的写入并发(应用侧改 batch 串行→半并发,从 32 线程降到 8)
  6. # 3) buffer pool 刷脏节奏对齐盘能力(sdb 实测可承受约 60MB/s 持续写)
  7. SET GLOBAL innodb_io_capacity = 1200;
  8. SET GLOBAL innodb_io_capacity_max = 2400;
  9. SET GLOBAL innodb_flush_neighbors = 0;  -- SSD 不需要邻页刷新
  10. # 4) 后台刷脏提前铺路
  11. SET GLOBAL innodb_max_dirty_pages_pct = 60;
  12. SET GLOBAL innodb_max_dirty_pages_pct_lwm = 10;  -- 低水位就开始匀速刷
复制代码

步骤 5:压测验证(下次变更窗口)
  1. # 用业务同规格写法压 sdb,确认治理后的可持续吞吐
  2. fio --name=wr --filename=/data/fio_test --direct=1 --rw=randwrite \
  3.     --bs=16k --iodepth=32 --numjobs=4 --time_based --runtime=120 \
  4.     --size=8G --group_reporting --invalidate=1
复制代码

压测拿到的 IOPS 和延迟曲线,就是下次做容量规划和告警阈值的依据。

四、实操检查清单


  • 每次登录先跑 iostat -x 1 10 + vmstat 1 10,把 b 列、wa、各盘 w_await/r_await、aqu-sz 记进巡检表,留基线。
  • 判断瓶颈先分层:设备裸延迟(低峰 fio 单深度)→ 排队(aqu-sz 与 await 走势)→ 请求来源(pidstat -d 定位进程)。
  • %util 只当参考,NVMe/RAID 设备不看 %util 判断饱和;svctm 一律忽略。
  • 数据文件、redo/binlog、备份归档分盘存放;归档/备份任务一律 ionice -c3 + 挪出业务批处理窗口。
  • innodb_io_capacity 与实测盘能力对齐,flush_neighbors 在 SSD 上关掉,低水位刷脏(pct_lwm)开起来。
  • fio 压测只打测试文件,绝不在业务数据文件系统上直接 rw=randwrite 覆盖;压测前确认路径。
  • 每月用 fio 复测一次裸延迟,环比变化超 30% 触发硬件检查(RAID 卡电池、盘 SMART)。
  • 监控侧对数据盘加两级告警:w_await 持续 >20ms 预警、>50ms 告警。


几个容易踩的坑

把 iowait 高直接当存储慢。 先区分"有 IO 在等"和"设备处理不动",后者要用裸延迟压测证明,否则换盘是玄学投资。

%util 100% 就判盘饱和。 多队列设备上这是常态,看 aqu-sz 和 await 才有效。

用 svctm 下结论。 这个指标早被内核废弃,iostat 新版本算出来的值没有意义。

fio 直接打业务路径。 曾有同行在数据目录里 rw=randwrite 把文件覆盖了——压测文件名、路径、size 三项每次都要人工确认两遍。

只调数据库参数不动应用。 批量导入 32 并发写单表,任何存储都扛不住,先限并发再看存储。

治理前后对比

指标治理前治理后
sdb w_await(批处理窗口)86.4ms1.8ms
sdb aqu-sz12.31.6
Innodb_log_waits(窗口期)4200+0
批处理耗时(同量数据)3h52m2h05m
单条 insert P99210ms6ms
归档任务与批处理撞车每周 2~3 次0(错峰+ionice)


这次问题的根因其实不是"盘不行",而是两股写流量在同一块盘上撞车外加刷脏策略不匹配。方向对了以后,没花一分钱硬件预算就把延迟压回来了。下次再看到 await 飙高,先按这套顺序走一遍:基线 → 定位进程 → 数据库侧交叉验证 → 裸延迟压测定性 → 错峰/限流/参数对齐。存储问题十有八九,最后修的是调度和配置。
免责申明1、欢迎访问本站,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@dbabbs.com
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

QQ|Archiver|小黑屋|DBA论坛中国 ( 鲁ICP备20017503号-2 )

GMT+8, 2026-10-10 11:01 , Processed in 0.018427 second(s), 9 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表