df 说磁盘满了,du 却查不出大文件,问题在被删掉的文件

举报
phantomxjc 发表于 2026/10/15 08:50:07 2026/10/15
【摘要】 前阵子家里那台跑同步任务的小机突然报磁盘满。登录上去一看: 可用空间直接是 0,服务开始报 No space left on device。我下意识去 du 找大文件: 全部加起来也就三十多 G,跟 df 报的 93G 差了一大截,中间那六十多 G 凭空消失了。第一次遇到的时候我愣了好一会儿,还以为 df 坏了。 先搞清楚它俩不是一回事 后来想明白了,这两个命令看的东西根本不同: df 问的是文件

前阵子家里那台跑同步任务的小机突然报磁盘满。登录上去一看:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        98G   93G     0 100% /

可用空间直接是 0,服务开始报 No space left on device。我下意识去 du 找大文件:

$ du -sh /* 2>/dev/null | sort -rh | head
28G    /home
3.1G   /var
1.2G   /usr
...

全部加起来也就三十多 G,跟 df 报的 93G 差了一大截,中间那六十多 G 凭空消失了。第一次遇到的时候我愣了好一会儿,还以为 df 坏了。

先搞清楚它俩不是一回事

后来想明白了,这两个命令看的东西根本不同:

  • df 问的是文件系统:"你一共分配出去多少块?"——它读的是超级块里的统计。
  • du 是老老实实顺着目录树,把能遍历到的文件大小一个个加起来。

所以只要存在"占着块、但在目录树里找不到"的文件,两边就对不上。这种情况主要有几种,最常见的是第一种。

元凶:被 rm 了但还被进程攥着的文件

Linux 里 rm 只是把目录项摘掉(unlink),文件的 inode 和数据块要等所有打开它的进程都关闭之后才会真正释放。如果某个进程还握着这个文件描述符,那块空间就一直占着,既不算自由块,也不在 du 的遍历范围里。

找它最直接的办法:

$ lsof -nP +L1
COMMAND   PID  USER   FD   TYPE DEVICE    SIZE/OFF NLINK   NODE NAME
python3  2841  root    6w   REG    8,2 68719476736     0  918273 /home/sync/task.log (deleted)

一行就把真相说明白了:(deleted) 是关键标志,SIZE/OFF 那列 68719476736 字节,算下来正好 64G,跟消失的空间基本对得上。

+L1 的意思是"列出链接数小于 1 的文件",也就是已经没有目录项指向它的。如果系统上 lsof 版本老不支持这个参数,用下面这种也行:

lsof -nP 2>/dev/null | grep '(deleted)'

效果一样,就是输出杂一些。

我这台的实际情况是:同步脚本的日志跑了一个多月没做轮转,涨到 64G。我清理磁盘的时候顺手 rm 掉以为完事了,结果写日志的 python 进程一直没重启,fd 还开着,空间一分没回来。

怎么把空间要回来

办法一:重启进程。 最干净。进程一退,fd 关闭,inode 引用归零,空间立刻释放。能停服务就用这个。

办法二:别重启进程,直接把 fd 截断。 有些服务不能随便停,可以这么干:

# 先看这个进程开了哪些已删除的 fd
ls -l /proc/2841/fd | grep deleted
# 假设看到 6 -> /home/sync/task.log (deleted)

# 把内容清空(注意是截断,不是删除)
: > /proc/2841/fd/6

执行完再 df -h,空间马上回来了。这个操作不影响进程继续持有 fd,它还会往里写,只是文件从 0 开始。

不过这招有个前提:文件是以写方式打开的,而且没有用 O_APPEND 之外的奇怪标志。而且话说回来,截断别人正在写的文件属于应急手段,事后还是应该把日志轮转配好,不然过段时间又是一样的问题。

从根上避免:别用 rm,用 truncate

踩过这次之后,我清理日志再也不用 rm 了,改成:

truncate -s 0 /home/sync/task.log
# 或者
: > /home/sync/task.log

这样 inode 还是那一个,进程手里的 fd 依然有效,文件大小瞬间归零,磁盘空间立刻回收,一行命令全解决。

如果是 nginx、php-fpm 这类自己管日志的服务,用 logrotate 的话记得加 copytruncate:

/home/sync/task.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
    dateext
}

copytruncate 的原理是先复制一份再截断原文件,好处是不需要给进程发信号,对那些不支持重新打开日志的程序特别友好。缺点是复制和截断之间有一小段时间窗,极端情况下可能丢几行日志——对家庭场景这点损失完全能接受。

另外两种对不上的情况

如果 lsof +L1 什么都没查出来,还有两个常见可能。

一是挂载点把原来的文件遮住了。 比如 /mnt/data 下面本来就堆了几十 G 东西,后来你在同一个目录挂了个新盘,那么原来那些文件谁也访问不到,但空间照样占着,du 也扫不到。验证方法:

mkdir -p /tmp/rescue
mount --bind / /tmp/rescue
du -sh /tmp/rescue/mnt/data

用 bind 挂载绕开子挂载点,就能看见"被遮住"的那部分真实体积了。

二是 inode 用光了,不是空间用光了。 表现很像:写文件时报 No space left on device,但 df -h 明明还有剩余。这时候看:

df -i

如果 IUse% 是 100%,说明是海量小文件把 inode 耗光了,常见于没清理的缓存目录、邮件队列、或者容器镜像的 layer 目录。定位大目录:

find / -xdev -type d -size +100M 2>/dev/null

小结

现在遇到"磁盘满",我的排查顺序基本固定了:

  1. df -h 确认哪个挂载点满了;
  2. df -i 排除 inode 耗尽;
  3. du -sh * 找不到就 lsof -nP +L1 找被删未释放的文件;
  4. 还是没有,考虑挂载点遮蔽,用 bind 挂载看一眼。

九成的情况在第 3 步就能收尾。这个坑本质上就是"删文件"和"释放空间"在 Linux 上是两件事,想明白了之后就很难再被它坑到了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。