df 说盘满了、du 说没满:三类「看不见」的磁盘占用(含实测数据)
先说结论
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=alwaysrsync -Star -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、从未真正写过的空洞、以及被多个名字引用的同一份数据。把这三类分开看,磁盘告警基本都能在五分钟内定位到具体进程或具体文件。
- 点赞
- 收藏
- 关注作者
评论(0)