服务器磁盘IO过高怎么办

结论: 磁盘 IO 过高不是"空间不够",而是"读写太忙"——df -h 看不出问题,要看 iostat -x 1。判断标准是三个数:%util 持续超过 80%、await 显著高于设备基线(HDD 超过 20 ms、SATA SSD 超过 10 ms、NVMe 超过 5 ms)、aqu-sz 持续大于 4。先用 iotop -o 和 pidstat -d 1 找到具体进程,再用 fio 测出设备真实能力,最后针对成因处理:临时用 ionice 限流备份任务止血,长期调 innodb_io_capacity、日志改异步写、换 SSD 或修 RAID 缓存。

很多运维看到服务器卡,第一反应是 top 里 %iowait 高,然后去清磁盘——但清空间对 IO 压力毫无帮助。IO 压力的本质是"单位时间内读写请求超过了设备能承受的量",与磁盘还剩多少 GB 无关。本文所有命令基于 Ubuntu 24.04 LTS + sysstat 12.x(截至 2026 年主流版本),RHEL 系路径基本一致,仅包名同为 sysstat。

先分清:磁盘空间不足与磁盘 IO 高是两件事

结论:两张表对照着看,能立刻判断该走"清理空间"还是"降低 IO 压力"的路线。

维度磁盘空间不足磁盘 IO 过高
典型现象写入报 No space left on device业务变慢、超时,写入仍成功
核心指标df -h 使用率接近 100%、df -i inode 耗尽iostat -x 1 的 %util、await、aqu-sz 高
查看命令df -h、df -i、du -shiostat -x 1、iotop -o、pidstat -d 1
主要成因日志堆积、大文件未清理刷盘频繁、随机写、备份任务、swap 交换
处理方向删除/归档/扩容限流、调参、换介质、改写入模式

两者也会互相诱发:剩余空间越少,文件系统分配新块越困难,碎片化加剧,反而推高 IO。所以当 %util 高时,顺手确认一下使用率是否超过 80%,超过就先清理,能给文件系统留出更多连续空间。

bash
# 一条命令同时看空间、inode 与 IO
df -h; df -i; iostat -x 1 3

# 没有 iostat 就先装 sysstat(Ubuntu/Debian)
sudo apt install -y sysstat iotop fio
sudo sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
sudo systemctl enable --now sysstat

iostat -x 1 输出解读:每个字段的正常值与告警阈值

结论:看五个字段就够了——r/s+w/s(IOPS)、await(延迟)、aqu-sz(队列)、%util(饱和度)、rareq-sz/wareq-sz(请求大小,用来判随机还是顺序)。

字段含义HDD(机械盘)SATA SSDNVMe SSD告警阈值
r/s + w/s每秒读写请求数(IOPS)80~2005k~50k10 万~100 万接近设备标称上限的 80%
await平均每次 IO 的等待+服务时间(ms)5~15 ms0.5~3 ms0.1~1 msHDD > 20 ms;SSD > 10 ms;NVMe > 5 ms
aqu-sz平均请求队列长度< 1< 1< 1持续 > 4 说明排队严重
%util设备处理 IO 的时间占比< 60%< 60%< 60%持续 > 80%
rareq-sz / wareq-sz平均请求大小(KB)顺序 64~1024;随机 4~16同左同左长期 4~8 KB 说明大量随机小 IO
svctm平均服务时间不可信,man 手册明确警告——不要用该字段判断
rkB/s + wkB/s每秒读写吞吐量100~200 MB/s400~550 MB/s1~7 GB/s接近标称带宽上限

两点重要提醒:

  • 新版 sysstat(12.x)把 await 拆成了 r_await 与 w_await,需要分别看读延迟和写延迟;写延迟单独飙高通常是刷盘或日志同步写导致。
  • %util 对多队列设备(NVMe、RAID、云盘)意义有限:它只统计"有请求在处理"的时间占比,16 队列的 NVMe 即使 %util 显示 100%,也可能远未饱和。此时应以 await 与 aqu-sz 为准。
bash
# 每 1 秒刷新,只看扩展统计(去掉 CPU 段让输出更干净)
iostat -x -d 1

# 只看某块盘,输出 10 次
iostat -x -d /dev/sda 1 10

# 按 MB 显示,更直观
iostat -x -d -m 1

# 配合 vmstat 看是不是 swap 在捣乱(si/so 非 0 即发生交换)
vmstat 1

定位进程:iotop 与 pidstat 找出"谁在猛写"

结论:先用 iotop -o 一眼抓出正在做 IO 的进程,再用 pidstat -d 1 拿到每秒读写字节数做定量判断。

bash
# 只显示真正在做 IO 的进程(最常用)
sudo iotop -o -P -d 1

# 累计 IO 量排序,适合抓"间歇性大量写"的进程
sudo iotop -a -o -P

# 每 1 秒输出各进程的读写速率(kB_rd/s、kB_wr/s)
pidstat -d 1

# 指定进程的精确 IO 统计(内核级计数,比 iotop 更准)
cat /proc//io
# 关注 read_bytes / write_bytes(实际落盘量)与 rchar / wchar(系统调用量)

# 查看该进程打开了哪些文件(删除未释放的大文件常在这里现形)
sudo lsof -p  | grep -i "deleted\|log\|data"

判断技巧:

  • iotop 里 DISK WRITE 一列持续几百 MB/s,且进程名是 mysqld,多半是脏页刷盘或大查询写临时表。
  • 进程名是 rsync、tar、xtrabackup、gzip,基本就是备份任务在跑,直接限流或改时段。
  • 出现 jbd2/sda1-8、kworker 等内核线程占用高,说明文件系统日志(journal)在频繁提交,通常是上层有大量 fsync 调用。
  • %iowait 高但没有任何用户态进程 IO 高,检查 vmstat 的 si/so,可能是 swap 交换。

随机写还是顺序写:决定优化方向

结论:看 rareq-sz/wareq-sz 与 rkB/s ÷ r/s 的比值——大请求是顺序、小请求是随机,两者的优化手段完全不同。

特征顺序读写随机读写
平均请求大小> 64 KB,常达 128 KB~1 MB4~16 KB
典型场景大文件拷贝、视频服务、备份、日志追加数据库 OLTP、小文件读写、碎片化缓存
瓶颈指标吞吐量(MB/s)IOPS 与延迟
优化方向提升带宽、合并写入、预读换 SSD/NVMe、加大缓存、合并随机为顺序
bash
# 计算平均请求大小:吞吐 ÷ IOPS
# 若 wkB/s = 4096 且 w/s = 1024,则平均 4 KB/次 → 典型随机写
iostat -x -d 1 | awk '/^sd|^nvme/ {printf "%s w/s=%s wkB/s=%s avg=%dKB\n", $1, $2, $4, ($2>0? $4/$2 : 0)}'

# 用 /sys 直接看设备的请求统计
cat /sys/block/sda/stat
# 第 4 列为读次数、第 8 列为写次数

随机写为主的数据库场景,优化重点是把随机写变成顺序写(如 MySQL 的 redo log 就是顺序写、change buffer 延迟写二级索引)以及加大内存缓存减少落盘次数;顺序写为主的场景则要提高单次写入大小、减少小 IO 次数。

用 fio 量化磁盘真实能力

结论:不知道设备上限就无法判断"高不高",用 fio 测出 IOPS 与带宽基线,才知道该优化到什么程度。

bash
# 随机写 IOPS(数据库最关心的指标)
fio -name=randwrite -ioengine=libaio -direct=1 -rw=randwrite -bs=4k \
    -numjobs=1 -iodepth=32 -size=1G -runtime=60 -time_based -group_reporting \
    -filename=/data/fio.test

# 随机读 IOPS
fio -name=randread -ioengine=libaio -direct=1 -rw=randread -bs=4k \
    -numjobs=1 -iodepth=32 -size=1G -runtime=60 -time_based -group_reporting \
    -filename=/data/fio.test

# 顺序写带宽(备份、日志类场景)
fio -name=seqwrite -ioengine=libaio -direct=1 -rw=write -bs=1M \
    -numjobs=1 -iodepth=8 -size=2G -runtime=60 -time_based -group_reporting \
    -filename=/data/fio.test

# 模拟数据库混合随机读写(读 70% / 写 30%)
fio -name=db-mix -ioengine=libaio -direct=1 -rw=randrw -rwmixread=70 -bs=16k \
    -numjobs=4 -iodepth=32 -size=2G -runtime=120 -time_based -group_reporting \
    -filename=/data/fio.test

跑 fio 的三条铁律:

  1. 务必加 -direct=1,绕过页缓存,否则测的是内存速度,结果虚高几十倍。
  2. 别在业务系统盘上跑满负载,测试文件放到独立目录或空闲数据盘;生产环境建议把 -runtime 控制在 60 秒以内并避开高峰。
  3. 测完删除测试文件:rm -f /data/fio.test,否则会占空间并影响后续测试准确性。

结果重点看三行:IOPS=(随机场景核心)、BW=(吞吐,顺序场景核心)、clat 的 99.00th 百分位延迟(长尾延迟比平均值更能反映用户体验)。

常见高 IO 成因与处理对照表

结论:七类成因覆盖了绝大多数线上场景,按特征列对号入座。

成因识别特征验证命令处理方式
MySQL 刷盘(脏页/checkpoint)mysqld 进程写高、redo log 太小SHOW ENGINE INNODB STATUS调大 redo、改 innodb_io_capacity
日志同步写jbd2、rsyslog、Nginx 等写高iotop -o 看进程名日志加 buffer、改异步、降级别
大量小文件wareq-sz 只有 4~8 KB、inode 消耗快df -i、目录文件数统计session 迁 Redis、缓存合并
RAID 卡无缓存或 BBU 故障写延迟突然暴涨数倍storcli/megacli 看 Cache Policy更换 BBU/电容,恢复 WriteBack
swap 交换vmstat 的 si/so 非 0vmstat 1、free -h调低 swappiness、加内存
备份任务占用rsync/tar/xtrabackup 写高`ps aux \grep rsync`ionice 限流、错峰执行
云盘 IOPS 打满IOPS 卡在固定值不再上涨对比控制台监控与 iostat升配云盘、换 ESSD 高 IOPS 档

BBU(电池备份单元)故障是最容易被忽略的一类:RAID 卡检测到 BBU 或超级电容失效后,会自动把写缓存策略从 WriteBack 降级为 WriteThrough,写性能可能暴跌 5~10 倍。

bash
# LSI/Avago 系列查看缓存策略(WriteBack 正常,WriteThrough 说明降级)
sudo storcli /c0 /v0 show | grep -i "cache"
# 老版本 MegaCLI
sudo MegaCli64 -LDInfo -Lall -aAll | grep -i "Cache Policy"
sudo MegaCli64 -AdpBbuCmd -aAll | grep -i "Battery State"

临时止血与长期优化

结论:先止血恢复业务,再动参数——顺序反了很容易在高压状态下改配置引发二次故障。

临时止血三招:

bash
# 1) 用 ionice 给备份/批处理进程降级(-c2 -n7 为最低优先级,-c3 为仅在空闲时执行)
sudo ionice -c2 -n7 -p 
sudo ionice -c3 -p 
# 启动时直接指定
ionice -c2 -n7 rsync -av /data/ user@backup:/data/

# 2) 暂停非关键定时任务(备份、日志归档、报表生成)
sudo systemctl stop backup.timer
crontab -l | grep -n "backup\|dump"

# 3) cgroup v2 直接限制进程的写带宽(如限到 20 MB/s)
sudo mkdir -p /sys/fs/cgroup/limit-io
echo  > /sys/fs/cgroup/limit-io/cgroup.procs
echo "8:0 wbps=20971520" > /sys/fs/cgroup/limit-io/io.max

长期优化——MySQL 侧参数(/etc/mysql/mysql.conf.d/mysqld.cnf):

ini
[mysqld]
# 按介质设定 IO 能力:HDD 200~1000,SATA SSD 1000~4000,NVMe 5000~20000
innodb_io_capacity     = 2000
innodb_io_capacity_max = 4000

# SSD/NVMe 关闭邻居页合并(HDD 保持 1 可减少随机寻道)
innodb_flush_neighbors = 0

# redo log 容量(MySQL 8.0.30+ 用此参数替代 innodb_log_file_size)
# 太小会导致频繁 checkpoint 与刷盘风暴
innodb_redo_log_capacity = 2G

# 提交刷盘策略:1 最安全最慢;2 每秒刷一次,宕机最多丢 1 秒
innodb_flush_log_at_trx_commit = 2
sync_binlog = 100

# 缓冲池越大,脏页落盘越少(专用机建议物理内存的 60%~70%)
innodb_buffer_pool_size = 8G

日志侧的长期优化:

nginx
# Nginx:为 access_log 加缓冲,减少每次请求的同步写
http {
    access_log /var/log/nginx/access.log main buffer=64k flush=5s;
    # 静态资源访问量大时可单独关闭或降低记录级别
    access_log off;   # 仅在确定不需要访问日志时使用
}

其余长期手段还包括:把 PHP/Java 的 session 与碎片化缓存迁到 Redis;用 tmpfs 承载 /tmp 与可重建的缓存目录;将备份窗口挪到业务低峰并配合 ionice -c3;机械盘场景优先升级到 SSD 或 NVMe(截至 2026 年,主流云厂商 ESSD 入门档已能提供数千 IOPS,成本相对机械盘已无显著劣势)。