df -h 显示 100%,du 加起来只有一半;df 明明还有空间,程序却报 No space left on device;磁盘没满但 IO 100%,数据库拖到卡死——这三个是 Linux 上最常见的存储故障,判断路径完全不同,处理错方向会浪费大量时间。
这篇按「先分清是空间问题还是 IO 问题」入手,给出一套从 df 到 iostat 的排查路径,每个症状对应具体命令和典型原因。所有命令都是运维日常在用的,可以直接复制。
第一步:分清三类故障
| 症状 | 最可能原因 | 第一条命令 |
|---|---|---|
| df 显示 100% | 真的有文件占满了 | du -sh /* 2>/dev/null | sort -h |
| df 显示 100%,du 加起来对不上 | 已删除但被进程占用的文件(deleted) | lsof +L1 |
| df 有空间但报 No space left | inode 用尽 | df -i |
| 空间正常但 IO 打满 | 大文件读写、日志刷盘、数据库刷脏页 | iostat -x 1 |
先花 10 秒跑上面四条命令,基本就能定位到大方向。别一上来就 rm -rf,删错文件比磁盘满更麻烦。
场景一:df 满了,du 也算得出来
按体积从大到小逐层排查,别直接扫全盘(大目录很慢):
du -sh /* 2>/dev/null | sort -h | tail -10:找到最大的顶层目录- 再对那个目录重复同样命令,两三轮就能找到具体位置
find /path -type f -size +500M -exec ls -lh {} \;:直接找大文件find /path -type f -mtime -1 -size +100M:找最近一天新增的大文件,日志暴增常用这招
最常见的五个占空间大户:
- 日志:
/var/log下的journal、nginx access.log、应用日志。journal 用journalctl --vacuum-size=500M收缩。 - MySQL binlog:
/var/lib/mysql下 binlog 能涨到几十 G。用PURGE BINARY LOGS BEFORE ...清理,并设置expire_logs_days。 - Docker:
docker system df看镜像、容器、卷各占多少;docker system prune -a清无用的。日志没设轮转的容器能写出几十 G。 - 备份文件:本地留了七份全量备份,是最常见的自作自受。
- 临时文件与缓存:
/tmp、包管理器缓存(apt clean/yum clean all)、CDN 或反代的缓存目录。
场景二:df 满了,du 对不上
这是新手最容易懵的情况。原因:文件被删除,但仍有进程持有文件句柄,空间不会释放。
- 查:
lsof +L1或lsof | grep deleted,看哪个进程占着已删除的文件 - 处理:重启对应进程(推荐),或者
truncate -s 0 /proc/PID/fd/FD(不重启但会丢该进程后续写入的偏移,谨慎)
典型场景:日志文件被 rm 掉了,但 nginx 还开着句柄,磁盘一直显示满。正确做法是用 logrotate 或 > file.log 清空,而不是 rm。
另一种对不上的情况:目录被挂载覆盖了。mount | grep 看是不是下面还挂了一个分区,导致 du 统计的是被覆盖前的空目录。
场景三:空间够但报 No space left
九成是 inode 用尽:小文件太多,每个文件占一个 inode。
- 查:
df -i,看IUse%是否接近 100% - 定位:
for i in /*; do echo -n "$i "; find $i | wc -l; done,找出小文件最多的目录 - 典型元凶:session 文件(
/var/lib/php/sessions)、邮件队列(/var/spool/postfix)、大量日志分片、缓存碎片
处理:清理无用小文件;长期方案是把这类目录单独挂一个分区,或者用 tmpfs/Redis 替代基于文件的 session。
场景四:IO 打满,但空间正常
先用 iostat -x 1 看是读还是写、是随机还是顺序。关键指标解读:
| 指标 | 含义 | 异常判断 |
|---|---|---|
%util | 设备繁忙度 | 持续 90% 以上说明饱和 |
await | 平均 IO 等待(ms) | SSD 超过 10ms、机械盘超过 30ms 就该查 |
r/s w/s | 每秒读写次数 | 突发很高说明有大量小 IO |
rkB/s wkB/s | 吞吐 | 与实际业务量对比,不对等说明有后台任务 |
aqu-sz | 请求队列长度 | 持续大于 2 说明排队 |
再定位到进程:
iotop -oPa:按累计 IO 排序,直接看哪个进程在读写pidstat -d 1:看每个进程的读写速率cat /proc/PID/io:看单进程的read_bytes/write_bytes
常见的 IO 来源按频率排:数据库刷脏页(innodb_flush_log_at_trx_commit=1 时每次提交都刷盘)、备份任务、日志同步写、大文件传输、容器层复制(overlay 的写放大)。
第五步:按原因处置,别只做清理
清完一次很快就再满,说明没解决根因。对每类问题给一个长期方案:
- 日志:配
logrotate(按天轮转、保留 7–14 份、压缩),或给 Docker 配max-size。应用日志分级,别把 debug 全打开。 - 数据库:binlog 设过期时间、慢查询日志限大小、临时表目录单独挂盘。
- 备份:本地只留最近 2–3 份,其余推异地。备份目录单独分区,别和系统盘抢空间。
- 监控:磁盘使用率告警设在 80%,inode 使用率也设一条。IO 等待(
iowait)持续超 20% 也报警。
扩容:什么时候加盘,什么时候换盘
两种情况要分开判断:
- 空间不够:加盘或扩云盘。扩云盘后还要扩文件系统(
xfs_growfs或resize2fs),很多人只扩了盘没扩文件系统,df还是旧容量。 - IO 性能不够:加盘解决不了。要看是不是随机 IO 太多、队列太深。换 NVMe、加内存(扩大文件缓存与数据库缓冲池)、把高 IO 目录拆到独立盘,都比加容量有效。
一个经验判断:%util 高但吞吐低,是随机小 IO 瓶颈,靠换更快的介质;%util 高且吞吐接近设备上限,是顺序大 IO,靠加带宽或分盘。
常见问题
Q:df 显示有空间,程序还是报写入失败?
按顺序查三处:df -i 看 inode、lsof +L1 看 deleted 文件、mount | grep ro 看是不是被挂成只读了(磁盘故障后内核会自动转只读)。
Q:删了大文件,df 没变化?
文件还被进程持有。用 lsof +L1 找到进程,重启它。这也是「删了日志但磁盘没释放」的标准答案。
Q:iowait 高一定是磁盘问题吗?
不一定。NFS/网络存储慢、swap 交换也会表现为 iowait 高。先 free -h 看 swap 是否在用,再 vmstat 1 看 si/so。
Q:能不能直接删 /var/log 下的东西?
能删已轮转的 .gz 和旧文件,别删正在写的当前日志(会导致句柄悬空)。宁可 truncate -s 0 清空,也别 rm。
Q:怎么提前发现盘要满?
三个告警:使用率 80%、inode 使用率 80%、每日增长量异常(比如比上周同日涨了 3 倍)。第三个最有价值,能在满之前发现「有东西在偷偷长大」。
小结
记住这条判断链:先 df -h 看空间,再 df -i 看 inode,空间和 inode 都正常就跑 iostat -x 1 看 IO,最后用 iotop 落到进程。四条命令覆盖 95% 的存储故障。
比排查更重要的是「不让它发生」:日志轮转、备份分离、binlog 过期、监控三个告警。这四件事做完,你基本不会再在半夜被磁盘满叫醒。
相关阅读:Docker 上生产:16 项设置、LNMP 调优清单、服务器性能自测脚本合集、服务器迁移不丢数据的完整清单。
本站深度稿系列:从0搭一个扛得住流量的站:LNMP调优清单、Docker上生产:从镜像到升级的16项设置、Windows服务器加固20项:从账号到离线备份。