df 说盘满了、du 说没满:三类「看不见」的磁盘占用(含实测数据)

举报
yd_276135645 发表于 2026/11/04 06:18:08 2026/11/04
【摘要】 df 说盘满了、du 说没满?用今天的实测数字拆开三类看不见的占用:被删除但仍被进程打开的文件(300 MB 一点不释放)、稀疏文件(512 MB 逻辑占 0)、硬链接重复计数(40 MB 数成 121 MB),并附排查命令、ext4 保留块与 inode 耗尽。

先说结论

df 看的是文件系统层面"已分配的块",du 数的是"在目录树里能走到的文件的 st_blocks"。两者对不上,通常不是工具坏了,而是有三类东西站在目录树之外。下面每条都附今天在 ext4 上的实测数字。

一、被删除但仍被进程打开的文件(最常见)

复现很简单:写一个 300 MB 的文件,unlink 掉它,但不关闭文件描述符。

import os
p = "/tmp/big.bin"
with open(p, "wb") as f:
    for _ in range(300):
        f.write(b"\0" * 1024 * 1024)
f = open(p, "rb")      # 持有 fd
os.unlink(p)           # 目录项没了,inode 还在

实测结果:unlink 之后 statvfs 的可用空间变化是 0 MB;ls -l /proc/<pid>/fd 里能清楚看到 3 -> /tmp/big.bin (deleted);只有 f.close() 之后,300 MB 才立刻归还。

原因是 unlink 只删目录项,inode 的引用计数归零时块才真正释放。只要还有一个进程握着 fd,这些块就一直挂着,而没有任何路径指向它——du 永远看不见,find 也找不到。

线上最典型的场景:rm /var/log/nginx/access.log 之后磁盘一点没降。正确做法是清空而不是删除:

truncate -s 0 access.log
# 或者
: > access.log

这样进程继续往同一个 inode 写,不会产生"幽灵文件"。

定位命令:

lsof +L1                       # link count 为 0 的已打开文件
lsof | grep deleted
ls -l /proc/*/fd 2>/dev/null | grep deleted   # 容器里没有 lsof 时用这个

二、稀疏文件:逻辑 512 MB,实际占 0

truncate -s 512M sparse.img
du -sh --apparent-size sparse.img   # 512M
du -sh sparse.img                   # 0
ls -l sparse.img                    # 536870912

ls -l 报的是 st_size(逻辑长度),du 报的是 st_blocks(真实分配的块)。今天实测:512 MB 的稀疏文件 du 为 0;同样环境下 64 MB 的普通文件 du 精确等于 64M,说明不是工具算错,而是文件本身真的没占块。

坑在拷贝。cp 默认会把空洞填成实块,512 MB 的逻辑文件拷完真的吃 512 MB。要保持稀疏必须显式指定:

  • cp --sparse=always
  • rsync -S
  • tar -S

虚拟机的 qcow2 镜像、数据库的预分配文件、容器镜像层里这类文件非常多,一次 cp -a 就能把 10 GB 的盘变成 100 GB 的需求。

三、硬链接:du 只算一次,du -l 算三次

一个 40 MB 的文件,加两个硬链接(nlink = 3),实测:

du -sh  dir/    → 41M
du -shl dir/    → 121M

默认 du 对同一个 inode 只统计一次(第一次遇到时计入),所以删掉其中任意两个名字都不会释放任何空间;-l 的意思是"每个目录项都算",同一个 inode 被数三遍。两个数字都不算错,它们回答的是不同问题。

这也是"删了备份目录磁盘却不降"的经典原因:备份脚本用 ln 做增量快照,你删掉的是链接,数据还被别的名字指着。查真实占用用 du -shx;查"谁还指着这个 inode"用:

find / -inum <inode号> -xdev

四、还有两类容易被忽略的

文件系统保留块。 ext4 默认给 root 保留 5% 的块,所以 df 的 Avail 总是比 Size - Used 小一截。纯数据盘上可以降下来:

tune2fs -m 1 /dev/sdb1

代价是普通用户把盘写满时碎片会更严重,系统盘不要动。

inode 耗尽。 df -h 显示还有几十 G,却报 No space left on device,这时候要看 df -i。小文件极多的场景(邮件队列、session 文件、海量小 JSON)先查这个。

一套够用的排查顺序

df -h; df -i
du -shx /* 2>/dev/null | sort -h | tail
lsof +L1
find /path -xdev -type f -size +1G -printf '%s %p\n' | sort -n | tail

-x 千万不要丢(du -x、find -xdev):不跨越挂载点时,du 掉进挂载在某个子目录下的另一块盘,会把别人的占用算到你头上;反过来也会漏掉真实占用。

小结

df 与 du 的差额不是"系统吞了空间",而是目录树之外还活着的块:被进程攥着的 inode、从未真正写过的空洞、以及被多个名字引用的同一份数据。把这三类分开看,磁盘告警基本都能在五分钟内定位到具体进程或具体文件。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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