第一章:MySQL的“假删除”困局
深夜11点,运维工程师小明收到数据库服务器磁盘告警:空间使用率95%!分析发现BIG_TABLE表的2年前日志是元凶。他果断执行清理:
DELETE FROM BIG_TABLE WHERE create_time < '2023-01-01'; -- 删除12亿条历史日志
确认事务提交后,监控显示表数据已消失。但次日清晨,服务器磁盘爆满,数据库陷入瘫痪——空间竟丝毫未释放!
分析:
InnoDB的“标记删除”机制下,DELETE操作仅将数据标记为逻辑删除(打上删除标签),物理数据仍占据磁盘。目的是支持MVCC(多版本并发控制),保证事务隔离性。
在空间归属权上,InnoDB不会主动将空间归还OS,仅允许新数据复用原有空间。
释放方案:
-- 彻底释放空间
OPTIMIZE TABLE BIG_TABLE; -- 重建表消除碎片
第二章:Linux的“僵尸文件”之谜
一周后,小明再遇磁盘告警!Apache日志文件access.log已增长至40GB。他火速执行:
bash
rm /var/log/apache/access.log # 删除日志文件
ls -l /var/log/apache/ # 确认文件消失
然而两小时后,业务系统崩溃——磁盘空间依然耗尽!
分析:
inode引用计数器:
rm仅删除文件路径(目录项),inode引用数减1
Apache进程仍持有文件句柄,引用计数>0。
空间持有者实锤:
lsof | grep deleted # 关键命令揪出"僵尸文件"
apache2 14836 root 1w REG 254,0 42G 65536 /var/log/apache/access.log (deleted)
如果一个进程在 rm 之前就已经打开了文件 A,那么它手里就握着一个指向该文件 inode 的文件描述符(File Descriptor)。 内核会为所有打开的文件维护一个引用计数器。只要还有进程持有文件描述符,即使目录中的链接已经被删除(link count = 0),内核也不会立即释放该文件的数据块,因为进程可能还要进行读写操作。此时,这个文件就变成了一个“你看不到它,但它却真实存在”的文件。这种设计是非常合理和安全的,1、保证进程稳定性:确保正在使用文件的程序不会突然因为文件被删除而崩溃或出错。2、允许文件无缝替换:例如,日志文件可以被 rm 掉然后重新创建,而正在写入的进程可以毫不知情地继续向原来的文件描述符写入,新的数据会写到新创建的文件中
释放方案:
# 方案1:优雅重启释放句柄 ,重启进程
kill -HUP 14836 # 通知Apache重建日志文件([网页12])
# 方案2:清空文件立即释放空间,进程不能重启的情况下的选择
echo "" > /proc/14836/fd/1 # 通过进程文件描述符清空
最终启示录
删除 ≠ 释放!
在计算机的世界里,可见的删除只是表象,空间的释放才是终极奥义。经验不足的同学,执行完删除操作可能就完事了,较有经验的同学会DOUBLE CHECK,确保删除真正生效。但经验丰富的同学,会用du命令检查空间的释放,提前发现问题。