> ## Content Index
> Fetch the complete content index at: https://blog.20061124.xyz/llms.txt
> Use this file to discover other available public pages before exploring further.

# 磁盘满了，但 du 找不到大文件
- URL: https://blog.20061124.xyz/disk-full-du/
- Published: 2026-10-01T02:00:00.000Z
- Updated: 2026-10-01T07:52:00.000Z
- Description: df 说根分区 100%，du 加起来只有 13G。差的 27G 藏在「已经被 rm、但进程还开着」的文件里。
- Author: Ray Jack
- Tags: 运维

某天早上收到告警：根分区使用率 100%，服务开始报「No space left on device」。

按常规思路清点了一遍：

```
$ df -h /
Filesystem  Size  Used  Avail  Use%  Mounted on
/dev/vda1    40G   40G     0   100%  /

$ du -sh /* 2>/dev/null | sort -h | tail -3
1.1G  /usr
2.3G  /var
8.7G  /opt

```

加起来不到 13G，可 `df` 说 40G 全用完了。**两个工具报的数字差了 27G**，这就是问题的入口。

## 一、先搞清楚两个数字为什么不一样

这两个命令查的根本不是一回事：

```
df     问的是文件系统：这个块设备一共多少块、还剩多少块（看超级块）
du     问的是目录树：从某个目录往下走，把每个文件占的块加起来（看 inode 和目录项）

```

只要出现「某个文件占了空间，但它**不在任何目录里**」，`df` 算得到、`du` 算不到，差额就出现了。所以 27G 的差距不是算错了，而是真有东西 `du` 看不见。

## 二、最常见的一种：被删掉但还开着的文件

用 `lsof` 列出所有链接数为 0 的打开文件，也就是「名字已经从目录里删掉、但还有进程握着它」的文件：

```
$ lsof +L1
COMMAND    PID USER   FD   TYPE DEVICE    SIZE/OFF NLINK   NODE NAME
mysqld    2841 mysql   12u   REG  253,1 12884901888     0 262155 /var/lib/mysql/ib_logfile0 (deleted)
docker-co 3902 root    7w   REG  253,1  7516192768     0 263901 /var/lib/docker/.../json.log (deleted)

```

找到了：一个 12G 的文件和一个 7.5G 的文件，加起来正好对上那 27G（还有零头是别的小文件）。

这是 Linux 上一个很容易反直觉的行为：

- `rm` 只是把目录项删掉，把 inode 的链接数减 1
- 只要还有进程打开着这个 inode，它的**数据块就不会被回收**
- 这个文件的目录项已经没了，所以 `du` 遍历目录树时永远碰不到它

```
rm 之前：
  目录项 /var/log/app.log  ──►  inode 263901  ──►  数据块 7.5G
                                    ▲
                                    └── 进程的 fd 也指着它

rm 之后：
  （目录项没了）
                               inode 263901 ──► 数据块 7.5G   ← 空间还占着
                                    ▲
                                    └── 进程的 fd 还指着它

```

最典型的场景就是日志：进程一直往日志文件里写，运维发现日志太大随手 `rm` 掉，磁盘却没释放——因为进程的 fd 从来没关过。

## 三、还有几个不那么常见的原因

上面那个原因占了大多数，但排查时最好一起过一遍，避免方向找错：

- **文件被挂载点挡住了。** 挂载之前如果往挂载目录里写过东西，挂上去之后这些文件就被盖住了。遇到这种情况，把设备 `umount` 掉，或者在别处重新挂一遍看看。
- **保留块。** ext4 默认给 root 保留 5% 的空间。`df` 的 `Use%` 是按普通用户视角算的，可能显示 100% 而实际上 root 还有余量。想让这块小一点可以 `tune2fs -m 1 /dev/vda1`。
- **inode 用完了。** 磁盘还有空间，但 inode 号已经分光，同样会报 No space left。这种要看 `df -i`，现象通常是「一堆很小的文件」。

## 四、处理：怎么把空间立刻还回来

最省事的办法是重启那个进程，fd 一关，空间立刻释放。但生产上未必能直接重启，尤其是数据库。

更好的做法是**直接把那个 fd 清空**，进程完全不用停：

```
# 拿到 pid 和 fd 号（lsof 输出里的 NAME 之前那列，比如 7w → fd 7）
truncate -s 0 /proc/2841/fd/7

# 或者用重定向，效果一样
: > /proc/2841/fd/7

```

原理是：`/proc/<pid>/fd/<n>` 是内核给这个打开的 fd 开的一个入口，往它写等同于操作那个文件本身。把它截断成 0 字节，占用的块就还给了文件系统，而进程依然拿着同一个 fd 继续用，不会崩。

这一步做完再回头看，数字就对上了：

```
$ df -h /
/dev/vda1    40G   13G    27G   33%  /

```

## 五、小结

「`df` 满了但 `du` 找不到大文件」，本质是**两个工具的视角不同**：一个看文件系统，一个看目录树。对不上就说明有文件脱离了目录树却仍被引用。

- 先算差额：`df` 的已用量减去 `du` 的合计，差额有多大、值得不值得查
- 查被删但仍打开的文件：`lsof +L1`，或者 `find /proc/*/fd -ls 2>/dev/null | grep deleted`
- 释放空间用 `truncate -s 0 /proc/<pid>/fd/<n>`，不必重启服务
- 根治的办法还是别 `rm` 日志——用 logrotate 加 `copytruncate`，或者让写日志的进程自己支持重新打开文件

另外，那个 `rm` 掉日志的动作很可能就是我自己做的：日志涨太快，第一反应是删掉。现在改成先看哪个进程在写它，再决定是轮转还是截断——删文件从来不是释放空间的正确姿势。