
1. 为什么你第一眼看到的磁盘占用往往是假的先说个真实场景。某天凌晨监控告警弹出来根分区使用率95%再过几个小时就要写满。我登录服务器第一件事就是执行du -sh /想看看哪个目录吃了空间结果命令卡了十几分钟都没出来。换df -h一看明明显示/已经用了95%但du统计完各个目录加起来的数字却远远小于df显示的总占用。这不是系统坏了而是很多新手甚至一些老手都会踩的坑df 和 du 统计的压根就不是同一个东西。这篇博文就把两件事彻底讲透一是du、df到底各自是怎么算出来空间占用的二是在真实的服务器环境里怎么用它们快速定位“谁把磁盘塞满了”。适合刚接触 Linux 的运维新手也适合那些遇到过df满了但du查不出头绪、想搞明白原理的开发者。先说结论df站在文件系统层面看“块设备还剩多少可用空间”du站在文件层面看“某个目录下的文件实际占用了多少数据块”。两者统计维度不同结果自然对不上。下面逐个拆解。2. 先搞清楚两个命令的区别别再混着用2.1 df站在文件系统角度看容量df的完整名字是 disk free它的核心工作是查询文件系统的元数据。每个文件系统比如 ext4、xfs在格式化的时候就固定了总块数、已用块数、可用块数这些信息存在文件系统的超级块里。df只是把这些现成的数字读出来然后换算成人类能看懂的单位。所以df非常快即使你在一台几百 TB 的存储服务器上执行df -h也是秒出结果。它不关心你具体的目录结构也不会逐个文件去遍历它只看“这块磁盘上一共划分了多少块、已经分出去多少块、还剩多少块”。这里有一个非常关键的点df统计的“已用空间”包含了所有已经被占用的数据块哪怕这个块对应的文件已经被删除了。只要进程还持有这个文件的文件描述符删除操作只是把文件名从目录项里摘掉但数据块仍然被标记为“已分配”df就会一直认为空间被占着。提示这就是经典的“磁盘空间满了但找不到大文件”现象的根源后面第5节会专门演示排查方法。2.2 du站在文件角度看实际占用du的完整名字是 disk usage它的逻辑是从当前目录开始递归遍历每一个子目录和文件调用stat系统调用拿到每个文件占用的块数然后累加。这个统计口径跟df完全不同。du只看“文件系统目录树里现存的文件每个占了多少块”它看不到已经被删除但还被进程持有的文件。所以只要有一个大文件被删了但进程没释放df显示占用还在du却无论如何都算不出来这个空间。另外注意du默认统计的是文件的“磁盘占用”而不是文件大小。因为文件在磁盘上是以块为单位存储的哪怕一个文件只有1个字节它也要占一个完整的块通常是4KB。所以你经常能看到du -h显示的文件大小比ls -lh显示的要大一些这是正常的。2.3 两个命令的统计口径对比维度dfdu统计对象文件系统的超级块元数据目录树下的所有文件是否遍历目录不遍历直接读取元数据递归遍历能否看到已删除但未释放的文件能看到仍计入已用块看不到执行速度极快目录越深、文件越多越慢典型用途看磁盘还有没有空间、分区使用率定位大目录、大文件一句话记住df是看“盘还剩多少”du是看“目录吃了多少”。排查问题的时候两者缺一不可。3. du 用法拆解从大到小查清每个目录3.1 最常用参数组合-sh、--max-depth、--time先给小白补个基础du后面如果不加任何选项会递归打印当前目录下每一层子目录的占用情况输出非常长基本没有人类能直接看懂。所以实际操作时你几乎总是要搭配参数用。最常用的组合是du -sh。-s表示只汇总当前目录的总大小不再一层层往下展开-h表示用人类可读的单位K、M、G、T显示。# 查看当前目录总占用 du -sh # 查看指定目录的总占用 du -sh /var/log # 查看当前目录下所有一级子目录各自占用 du -h --max-depth1 # 查看两级目录的占用情况 du -h --max-depth2 /var--max-depth是个很实用的参数它的含义是“只看往下最多几层目录”。我用--max-depth1最多因为生产环境里一般只需要先看一级目录谁最占空间定位到目标后再往下钻。这个方式比du -sh /var/*效率高而且不会因为某个子目录无法读取而报错终止。3.2 用 --exclude 排除干扰目录实际环境里经常有这种情况有些目录你明确知道不用管比如刚挂载的临时目录、特殊的备份挂载点但du还是会傻乎乎地遍历它。排除它们能省下大量时间。# 查看 / 目录下第一层大小排除 proc、sys、dev 这几个虚拟文件系统 du -h --max-depth1 / --exclude/proc --exclude/sys --exclude/dev # 查看 /var 下各级目录占用排除日志目录里最老的那批压缩文件 du -h /var --exclude*.gz --exclude*.zip一个小提醒--exclude一定要写在du命令的最后面而且路径最好写绝对路径。我见过有同事写成du -sh --exclude/proc /结果发现排除没生效因为/proc属于挂载的 tmpfsdu默认本来就不递归它真正想排除的/var/log反而没写上。3.3 快速找出目录下最大的几个文件du本身没有“排出前N名”的能力需要和sort、head配合。# 找出当前目录下占用最大的前10个文件 du -ah . | sort -rh | head -10这里的-a很关键它会让du同时统计所有文件默认只统计目录-h输出人类可读格式然后通过sort -rh按照“人类可读大小”排序。-r表示降序-h让 sort 能识别 K、M、G 这些单位并正确排序。不过我实测下来这种输出在文件特别多、目录层级特别深的场景下会很慢因为du要遍历每一个文件。更快的办法是配合find# 查找当前目录下所有大于500MB的文件并排序 find . -type f -size 500M -exec ls -lh {} \; | sort -k5 -rh或者用du -a先输出所有文件大小再管道给awk过滤du -a . | awk $1 500000 | sort -rn | head -20这个方式把单位统一成了 KB避免了-h人类可读格式在 sort 时遇到的单位换算问题实际用起来反而更可靠。4. df 用法拆解站在文件系统层面看全局4.1 常用参数-h、-T、-idf的参数比du少多了核心就三个-h、-T、-i。# 查看所有文件系统的使用情况人类可读格式 df -h # 同时显示文件系统类型 df -hT # 查看 inode 使用情况重要 df -i-h没什么好说的就是人类可读单位。-T会多显示一列“文件系统类型”比如 ext4、xfs、tmpfs、overlay 等。这个信息在排查问题的时候很有用因为不同的文件系统类型可用空间的计算方式有细微差别比如 ext4 默认预留了 5% 的空间给 root 用户而 xfs 也可以配置预留块。-i是很多人容易忽略的它查看的是 inode 使用率。文件系统里每个文件都要占用一个 inode索引节点当你创建了海量的小文件比如 log 文件、缓存文件即使磁盘空间还有富余inode 也可能先耗尽导致无法创建新文件。这种情况在容器环境、缓存服务器和消息队列服务器上非常常见。4.2 用 df 快速定位挂载点问题df适合回答三个问题磁盘是不是真的满了是哪个分区的磁盘满了可用空间到底还够不够比如你在/data目录下执行df -h看到的是/data所在文件系统的使用情况而不是整个系统盘的情况。如果/data是个独立挂载的 xfs 分区那它的使用率和根分区完全不同。有一个细节特别值得注意当你进入一个很深很深的目录然后执行df -h .它显示的是当前目录所在的文件系统而不是你想象的“当前目录的大小”。这条命令适合用来确认“我现在在哪个挂载点下”尤其在服务器上有十几个数据盘挂载的时候特别有用。# 查看当前目录所在文件系统的使用情况 df -h . # 查看指定目录所在文件系统的类型和挂载点 df -hT /var/log4.3 处理 inode 耗尽的行之有效的套路df -i显示 100%但df -h还有大量空间这时候你创建空文件都会报No space left on device错误特别迷惑。排查套路是这样的# 1. 先用 df -i 确认 inode 是否耗尽 df -i # 2. 找到 inode 占用最高的分区后定位它是哪个挂载点 df -hi /坏掉的挂载点 # 3. 在对应挂载点下找出文件数量最多的目录 find /var/spool -type f | wc -l find /var/spool -type d | wc -l # 4. 按目录统计文件数量排名 for i in /var/spool/*; do echo -n $i: ; find $i -type f | wc -l; done这种情况下清空小文件、增加 inode有些文件系统支持resize2fs扩容是两个主要解决手段。但更推荐的做法是尽早配置日志切割和队列清理策略别等 inode 满了再去救火。5. 实际操作从告警到定位的完整排查流程5.1 场景一df 满了但 du 查不出大文件这是我遇到最多的场景。排查步骤如下# 第1步确认哪个分区满了 df -h # 第2步进入满掉的分区根目录看一级目录占比 cd / du -h --max-depth1 -x . 2/dev/null | sort -rh | head -20注意这里的-x参数它的作用是“只在当前文件系统内遍历”不跨越挂载点。这个参数极其重要如果没有它du会把挂载在当前分区下的其他磁盘、虚拟文件系统全部统计一遍结果失真。我遇到过一位同事排查根分区满了输入du -h --max-depth1 /之后发现排名第一的目录是/mnt/backup占了好几百G。但他用df -h一看/mnt/backup明明是另一个托底磁盘挂载过来的根分区还是满的。问题就出在/mnt/backup这个挂载点本身统计了独立分区的容量而不是根分区的。加了-x之后根分区真正的占用大户才浮出水面。# 第3步进入上一轮找到的排名靠前的目录继续下钻 cd /var du -h --max-depth1 -x . 2/dev/null | sort -rh | head -20 # 第4步重复这个过程直到定位到具体文件5.2 场景二日志文件占满空间的处理日志文件是最常见的磁盘杀手尤其生产环境里的 Nginx、Tomcat、Java 应用的日志。定位到大文件后常规做法是# 看看是哪个日志文件 ls -lh /var/log/nginx/access.log # 直接清空推荐用 truncate不要用 rm truncate -s 0 /var/log/nginx/access.log这里特别强调清空日志文件一定用truncate -s 0或者 文件别用rm。原因有两个一是很多服务比如 Nginx、Java 进程会一直持有日志文件的文件描述符你rm删掉之后进程还在往这个被删除的文件里写数据空间不会立刻释放必须重启进程或者kill -HUP才能生效二是truncate是直接把文件截断成0字节保留文件句柄和权限进程写日志完全不受影响。如果真用rm删了日志文件但空间还是没释放可以这样找出那个被占用文件# 找出所有已删除但还占用空间的进程并记录文件描述符 lsof | grep deleted # 然后根据 PID 强制重置进程日志句柄 # 不同的服务方式不同比如 nginx 用nginx -s reopen # Java 应用通常需要重启或借助 logback 的重新加载机制5.3 场景三需要快速定位超大文件如果你明确记得某个目录下有个超大文件可以直接用find加-size参数来定位# 找出根分区下所有超过1GB的文件用 -xdev 限制在根分区内 find / -xdev -type f -size 1G -exec ls -lh {} \;这个命令里-xdev和du的-x作用一样都是不跨文件系统防止把其他挂载盘全都扫描一遍。-size 1G表示大于1GB的文件-exec ls -lh让你能直接看到文件大小和修改时间。实测下来当文件总量达到几TB、文件数几百万的时候find -size比du -a | sort方案快得多因为find在遍历的时候可以跳过很多小文件du却必须读取每一个文件的元数据。6. 常见问题与排查技巧实录6.1 为什么 df 和 du 显示的结果差异巨大把这个问题单独拿出来讲因为几乎每个用 Linux 的人都会遇到一次。总结下来原因通常有四种已被删除但仍被进程占用的文件这是最常见的原因。用lsof | grep deleted找出这些进程重启或让进程重新打开日志文件即可。跨文件系统统计du没有加-x把挂载的其他分区也统计进来了。系统保留块ext4/xfs 文件系统会预留一部分块给 root 和文件系统维护用df会把它们算进已用空间du统计不到。文件系统元数据本身占用inode、目录项、日志等元数据也消耗空间但du是按文件累加的这部分不会显示出来。如果你遇到的场景是df显示已用了 90Gdu -sh /显示只有 70G优先排查第一种情况。6.2 排查工具补充ncdu 是 du 的交互式替代品命令行里纯靠du反复下钻确实累。这里推荐一个工具ncdu它本质上是du的交互式界面版安装之后一条命令就能看到所有目录的大小并且支持用上下键浏览、用d键删除文件。# Debian/Ubuntu apt install ncdu # CentOS/RHEL yum install ncdu # 直接查看指定目录 ncdu /var第一次进入ncdu它会扫描整个目录扫描完成后显示的结果按大小排序回车可以进入子目录g键可以按百分比或绝对大小切换显示。实测在百万文件级别的目录下ncdu的扫描效率比du稍微高一些因为它的排序和显示是合并处理的。但注意一点ncdu通过 SSH 连接服务器时如果网络延迟太高显示会卡顿。而且它默认的扫描范围和du一样不会自动排除挂载点遇到多盘环境还是要手动排除挂载点。6.3 查看单个文件大小的几种方式对比标题里特意提到了“查看某个文件占用磁盘空间的大小”这里做个统一回答。命令显示内容适合场景ls -lh 文件文件逻辑大小快速看一眼du -h 文件文件磁盘占用确认真实磁盘占用stat 文件文件大小 占用块数 inode信息排查元数据问题filefrag -v 文件文件碎片情况排查大文件碎片化其中ls -lh和du -h的差异前面已经解释过了一个是逻辑大小一个是实际占用块数。在绝大多数文件系统上小文件的du值会略大于ls值大文件的du值会略小于或等于ls值因为文件系统按块分配尾部不足一块的零头会浪费。如果你要查看某个文件是否稀疏文件文件逻辑上很大但磁盘占用很小可以用du -h和ls -lh对比差距悬殊就是稀疏文件。这种文件在数据库、虚拟磁盘镜像里很常见。6.4 跨文件系统统计时排除挂载点的坑再补充一个du和df联用的实战技巧。如果只想查看根分区下的真实占用可以这样# 方法一使用 -x只统计当前文件系统 du -shx / # 方法二用 df 查看各挂载点再逐个 du 排除 df -h mount | grep ^/dev方法一最推荐-x参数相当于--one-file-system它会把所有子挂载点比如 /proc、/sys、/dev、其他硬盘的挂载目录都跳过。一次到位不会把别的分区算进来。6.5 一些值得养成的排查习惯最后说几个我在实际运维中沉淀下来的习惯。每周定时输出一次磁盘占用报告。用crontab跑一条脚本把df -h结果和du --max-depth1的前10名追加到日志文件里等到磁盘出问题时直接翻历史记录就能看到是哪个目录在这一周内突然暴涨排查效率能快好几倍。命令很简单# crontab 示例每天凌晨2点记录一次磁盘占用情况 0 2 * * * df -hT /var/log/disk_report.log 21; du -h --max-depth1 -x / /var/log/disk_report.log 21把/tmp目录当作重点观察对象。很多程序的临时文件会写进/tmp一旦进程异常退出这些临时文件就成了垃圾文件。排查问题的顺序一定是先 df 后 du。先用df -h确认到底是哪个分区满了、是否涉及 inode 占用过高再用du定位具体目录。很多人一上来就du -sh /等于在做全盘扫描慢不说结果还会被挂载点干扰。先df限定范围再du下钻定位两步走既不浪费时间也不会被误导。7. 从命令到习惯磁盘空间排查的一点心得说实话du和df这两个命令本身难度不大参数翻来覆去也就那么几个。但我在生产环境踩过多次跟磁盘空间相关的坑之后最大的体会是排查问题的思路永远比命令本身重要。遇到磁盘告警可以先让自己冷静三秒钟想清楚一个问题——你现在看到的数字代表的是“文件系统层面的已用空间”还是“目录树层面的文件总大小”这两个层面之间的差值往往就是问题的答案。那些被删除但仍被进程持有的文件、那些跨文件系统的挂载点、那些被 inode 吃光的隐藏空间都是两个命令显示不一致背后的故事。搞懂这个逻辑你就不需要背命令了因为你自己就能推导出该用哪个工具、该加什么参数、下一步该往哪个方向排查。另外提一句对个人开发者和运维人员来说本地虚拟机快照和云服务器的监控告警能够大大降低磁盘排查成本。快照可以让你在清理文件之前先备份状态告警可以让你在磁盘满之前提前介入而不是等到文件都写不进去才去抢救。工具多备几把但先把du和df吃透大部分磁盘空间问题都能用它们解决不用一上来就装各种重量级的监控分析组件。